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
}