Windows Programming/讀寫鎖

維基教科書,自由的教學讀本

讀寫鎖(Slim Read Write Lock, SRW)是自Windows Vista 引入的線程同步用的輕量級工具。從React OS的實現看,SRW具有下述特點:

  • 適用於一個進程內部各線程的讀操作、寫操作同步
  • 各線程採用自旋鎖忙等待

SRW Lock是一個4字節長的指針。它的低4位被用於4個不同的標誌,對應的宏定義:

#define RTL_SRWLOCK_OWNED_BIT   0       //是否有线程正占有资源
#define RTL_SRWLOCK_CONTENDED_BIT   1   //是否有线程在等待 
#define RTL_SRWLOCK_SHARED_BIT  2       //是否有线程正在读取 
#define RTL_SRWLOCK_CONTENTION_LOCK_BIT 3  //作为线程等待块(WaitBlock)链表的表头指针修改时的自旋锁

#define RTL_SRWLOCK_OWNED   (1 << RTL_SRWLOCK_OWNED_BIT) //第0位为1表示有线程正在读/写共享资源  
#define RTL_SRWLOCK_CONTENDED   (1 << RTL_SRWLOCK_CONTENDED_BIT) //第1位为1表示一个或者多个生产线程在等待独占资源  
#define RTL_SRWLOCK_SHARED  (1 << RTL_SRWLOCK_SHARED_BIT)//第2位为1表示一个或者多个消费线程正在读取  
#define RTL_SRWLOCK_CONTENTION_LOCK (1 << RTL_SRWLOCK_CONTENTION_LOCK_BIT) //第3位为1标识有一个线程在修改WAITBLOCK结构的表头指针  

#define RTL_SRWLOCK_MASK    (RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED | \  
	RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENTION_LOCK)
#define RTL_SRWLOCK_BITS    4

SRW Lock的高28位的作用:

  • 只有讀操作線程時,則是所有讀操作線程的數量;
  • 有寫操作線程時,是線程等待塊(Wait Block)鍊表的表頭指針。因為它所指向的內存塊的對齊方式是16個字節,即地址的最低4位都是0。 寫/讀線程分別調用AcquireSRWLockExclusive或者AcquireSRWLockShared時,會將一個在線程棧上構建的結構體掛入SRW Lock所指向的鍊表。

等待塊的結構體定義:

typedef struct _RTLP_SRWLOCK_WAITBLOCK
{
 LONG SharedCount; //有多少线程 在等待读取  
 volatile struct _RTLP_SRWLOCK_WAITBLOCK *Last;
 volatile struct _RTLP_SRWLOCK_WAITBLOCK *Next;   //链表节指针  

 union
 {
    LONG Wake;                                   //非0表示可以被唤醒,0表示继续睡眠  
    struct
    {
       PRTLP_SRWLOCK_SHARED_WAKE SharedWakeChain;   //需要被唤醒的消费线程链表  
       PRTLP_SRWLOCK_SHARED_WAKE LastSharedWake;//最后一个要被唤醒的读操作线程。如果多个读操作线程等待,形成一个链表  
    };
 };
 BOOLEAN Exclusive;              //1表示该结构体对象由生产线程构建在栈上,0表示结构体由消费线程构建在栈上  
} volatile RTLP_SRWLOCK_WAITBLOCK, *PRTLP_SRWLOCK_WAITBLOCK;

typedef struct _RTLP_SRWLOCK_SHARED_WAKE //单向链表结构 ,代表每个需要被唤醒的读线程
{
	LONG Wake;   //唤醒标志,非0唤醒,0睡眠  
	volatile struct _RTLP_SRWLOCK_SHARED_WAKE *Next;
} volatile RTLP_SRWLOCK_SHARED_WAKE, *PRTLP_SRWLOCK_SHARED_WAKE;

總結狀態-動作表(有限狀態自動機):

讀寫鎖狀態 讀線程申請鎖 讀線程釋放鎖 寫線程申請鎖 寫線程釋放鎖 注釋
占用與等待線程均為空 高24位共享計數置1,置位RTL_SRWLOCK_SHARED與RTL_SRWLOCK_OWNED 不可能 置位RTL_SRWLOCK_OWNED 不可能 初始化狀態
讀線程占用 無等待線程 高24位共享計數加1,保持RTL_SRWLOCK_SHARED 與RTL_SRWLOCK_OWNED置位 高24位共享計數減1,不為0則保持SHARED與OWND置位 棧上的StackWaitBlock 成為第一個等待塊,置位 RTL_SRWLOCK_SHARED,RTL_SRWLOCK_OWNED與 RTL_SRWLOCK_CONTENDED 不可能 示例
有線程等待,第一個等待線程是寫線程 最後一個等待線程是寫線程 棧上的StackWaitBlock 加入等待線程列表的尾部 共享計數減1後如果為0,則等待塊鍊表表頭指向第二個等待塊或置空,原來的第一個等待塊Wake標誌置位 棧上的StackWaitBlock 加入等待線程列表的尾部 不可能 示例
最後一個等待線程是讀線程 棧上的SharedWake 塊加入最後一個等待塊的讀線程鍊表的尾部,最後一個等待塊的共享計數加1 不可能 示例
有線程等待,第一個等待線程是讀線程 棧上的SharedWake 塊加入第一個等待塊的讀線程鍊表的尾部,第一個等待塊的共享計數加1 不可能 不可能 示例
寫線程占用 無等待線程 棧上的SharedWake 塊成為第一個等待塊,置位RTL_SRWLOCK_OWNED 與 RTL_SRWLOCK_CONTENDED 不可能 棧上的StackWaitBlock 成為第一個等待塊,置位 RTL_SRWLOCK_SHARED,RTL_SRWLOCK_OWNED與 RTL_SRWLOCK_CONTENDED 置為全0 示例
有等待線程 最後等待線程是寫線程 棧上的StackWaitBlock 加入等待線程列表的尾部 不可能 棧上的StackWaitBlock 加入等待線程列表的尾部 調整等待塊表頭指針指向第二個等待塊或為空;原來第一個等待塊如果為寫操作則Wake標誌置位,如果為讀操作則SharedBlock鍊表的每一個塊Wake標誌置位 示例
最後等待線程是讀線程 棧上的SharedWake 塊加入最後一個等待塊的讀線程鍊表的尾部,最後一個等待塊的共享計數加1 不可能 示例

下面是各API的具體實現:

VOID NTAPI RtlInitializeSRWLock(OUT PRTL_SRWLOCK SRWLock)
{
    SRWLock->Ptr = NULL;   //直接赋值为0
}