Windows核心编程笔记(八)用户模式下的线程同步 SRWLock剖析
来源:互联网 发布:c语言入门网站 编辑:程序博客网 时间:2024/04/29 13:48
在VISTA及之后的系统中,引入了 SRWLock 用户用户模式的线程同步,MSDN中是这样描述的。
SRWLock 轻量级的读写锁,它与临界区对象的不同在于,它分为两个模式来访问共享资源。并假设有两种类型的线程同时工作,一种用在读取共享资源,通常又称为消费线程,另一种用来对共享的资源进程写入操作,通常又称为生产线程。
共享模式下每个读取线程可以读取共享的数据,不受任何限制,它们可以同时读取共享数据。
独占模式下只运行一个写入线程访问共享数据,其他读/写线程都会被堵塞。 直到该线程释放了读写锁。
在多线程的情况下,生产线程和消费线程是无序的,SRWLock是一个指针,它的优势在于可以快速的更新锁的状态,劣势在这个指针可以存放有限的信息,因此SRWLock不能够递归获取,此外一个线程处于共享模式的时候,不能讲自身升级为独占模式。
初始化读写锁很简单
VOID WINAPI InitializeSRWLock( PSRWLOCK SRWLock);在Winbase.h文件中
typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK;
typedef struct _RTL_SRWLOCK { PVOID Ptr;} RTL_SRWLOCK, *PRTL_SRWLOCK;
可见读写锁确实只是一个指针变量。
使用读写锁:
在消费线程中
AcquireSRWLockShared
*******读取操作
ReleaseSRWLockShared
在生产线程中
AcquireSRWLockExclusive
*******写操作
ReleaseSRWLockExclusive
这个读写锁和关键段一样方便使用,作者经过测试发现读写锁的性能较关键有很大的提升,这是因为读写锁是基于原子访问的,关键段是基于事件内核对象的,从用户模式到内核模式的切换占用了大量的时钟周期。
剖析SRWLock,以React OS下对读写锁的实现代码来看其工作原理。
在实现中SRWLock 的这个指针变量是这样来标识信息的
该指针的低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#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
SRWLock 的高28位是一个指针,为什么只有28位呢,是因为它要指向对象的对齐方式是16,即地址的最低4位都是0,比如都是这样的0x00124710,0x00124720,0x00124850, 当需要获取指针所指向的对象时,只要将低4位全部置0,就可以得到对象的地址。
SRWLock 在同时有一个以上线程读写资源的时候,它指向一个结构体链表。
在有其他线程在读/资源的时候,一个线程调用AcquireSRWLockExclusive或者AcquireSRWLockShared 时会将一个在栈上构建的结构体挂入SRWLock 所指向的链表,这样每个将要读/写资源的线程都会在栈上构建这么一个结构体,并将结构体挂入链表中。
该结构体是这样定义的
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;
初始化读写锁,只是简单的将指针置为0
NTAPIRtlInitializeSRWLock(OUT PRTL_SRWLOCK SRWLock){ SRWLock->Ptr = NULL;}
AcquireSRWLockExclusive
它对应的函数是RtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock), 由于这个函数的代码比较长,这里先贴出大体结构,然后分部分注释。
NTAPIRtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock){ __ALIGNED(16) RTLP_SRWLOCK_WAITBLOCK StackWaitBlock; //构建于栈上的 对齐为16字节的 等待块 PRTLP_SRWLOCK_WAITBLOCK First, Last; if (InterlockedBitTestAndSetPointer(&SRWLock->Ptr, RTL_SRWLOCK_OWNED_BIT)) //如果有其他线程在访问资源,进入循环,该原子访问函数返回之前的位值 { LONG_PTR CurrentValue, NewValue; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (CurrentValue & RTL_SRWLOCK_SHARED) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { goto AddWaitBlock; } else { } //Part1, 如果有线程在读取资源,且没有其他生产线程在独占资源 } else { if (CurrentValue & RTL_SRWLOCK_OWNED) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) {AddWaitBlock: //Part2,如果有其他线程在等待独占资源 } else {//Part3,如果有线程独占资源,且没有其他线程在等待资源 } } else { if (!InterlockedBitTestAndSetPointer(&SRWLock->Ptr, RTL_SRWLOCK_OWNED_BIT)) { break; } } } YieldProcessor(); 执行空指令 } }}
最后一个函数 只是执行一个NOP指令,只是用来做延时而已,
#define YieldProcessor() __asm__ __volatile__("nop");
Part1
if (CurrentValue & RTL_SRWLOCK_CONTENDED) { goto AddWaitBlock; } else { StackWaitBlock.Exclusive = TRUE; //标识结构体位于生产线程的栈上 StackWaitBlock.SharedCount = (LONG)(CurrentValue >> RTL_SRWLOCK_BITS); //目前的高28位标识多少个线程在等待读取 StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; //初始化这个栈上的结构体 NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENDED | RTL_SRWLOCK_OWNED;//设置新指针为该栈上结构地址并设置标志位 if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) 更换指针为新的值 { RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); 进入生产线程的等待函数 break; }
看下这个生产线程的等待函数
NTAPIRtlpAcquireSRWLockExclusiveWait(IN OUT PRTL_SRWLOCK SRWLock, IN PRTLP_SRWLOCK_WAITBLOCK WaitBlock){ LONG_PTR CurrentValue; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (!(CurrentValue & RTL_SRWLOCK_SHARED)) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { if (WaitBlock->Wake != 0) { break; } } else { break; } } YieldProcessor(); //只有在没有线程在读取,没有其他生产线程在独占,或者独占的线程将该线程的WAKE标志设为非0,时退出死循环 }}
该函数保证了没个生产线程以独占的模式运行,其他生产线程只能在这里死循环,只要被唤醒,或者独占标志位被清除。
Part2:
AddWaitBlock: StackWaitBlock.Exclusive = TRUE; StackWaitBlock.SharedCount = 0; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; 初始化结构体 First = RtlpAcquireWaitBlockLock(SRWLock); 根据28位的指针去掉标志位,获取指向的链表表头, if (First != NULL) { Last = First->Last; Last->Next = &StackWaitBlock; First->Last = &StackWaitBlock; //将该线程栈上的结构体挂入链表的最后面,将表头的Last指向最后这个挂入的 RtlpReleaseWaitBlockLock(SRWLock); RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); //进入等待循环 break; }
RtlpAcquireWaitBlockLock 揭示了 如何通过28位的指针工作
NTAPIRtlpAcquireWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock){ LONG_PTR PrevValue; PRTLP_SRWLOCK_WAITBLOCK WaitBlock; while (1) { PrevValue = InterlockedOrPointer(&SRWLock->Ptr, RTL_SRWLOCK_CONTENTION_LOCK); //低位 第3位如果是1表示,有其他线程在调用该函数根据指针获取链表头这里保证了 只有一个线程可以访问链表,其他需要访问的要等待 if (!(PrevValue & RTL_SRWLOCK_CONTENTION_LOCK)) break; YieldProcessor(); } if (!(PrevValue & RTL_SRWLOCK_CONTENDED) || (PrevValue & ~RTL_SRWLOCK_MASK) == 0) { RtlpReleaseWaitBlockLock(SRWLock); //如果现在没有处于独占模式 或者 高28位 为0 ,错误 释放RTL_SRWLOCK_CONTENTION_LOCK标志位 return NULL; } WaitBlock = (PRTLP_SRWLOCK_WAITBLOCK)(PrevValue & ~RTL_SRWLOCK_MASK); 将指针与0x11111110 与操作, 将低4位标志清除,构成实际地址 return WaitBlock;}
NTAPIRtlpReleaseWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock){ InterlockedAndPointer(&SRWLock->Ptr, ~RTL_SRWLOCK_CONTENTION_LOCK); 只是简单将该标志位设为0}
Part3:
StackWaitBlock.Exclusive = TRUE; StackWaitBlock.SharedCount = 0; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; 初始化结构体 NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; 将结构体地址构成新的指针 if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) 替换原来的指针 { RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); 进入等待 break; }
第一部分 和第三不分 代码差不多的,唯一的区别就是 第一不分多设置了一个标志位 RTL_SRWLOCK_SHARED,它们都是初始化栈上的结构体 并将结构体的地址加上标志位,然后替换为指针的值。
AcquireSRWLockExclusive 总结:
1、当有线程还在读取时,但是没有被其它生产线程独占, 那么挂入栈等待块,设置标志 (读取 独占 拥有),进入等待,读取线程全部Release时 线程等待结束,以独占模式访问资源
2、有其他生产线程在等待独占时,将栈块挂入SRWLock指针所指向链表的末尾,进入等待,在前面所有已挂入的等待都Release时,线程才结束等待 以独占模式访问资源。
3、如果有线程在独占,但是没有其他在等待独占的,那么挂入栈等待块,设置标志 (独占 拥有),进入等待,读取线程全部Release时 线程等待结束,以独占模式访问资源
ReleaseSRWLockExclusive
NTAPIRtlReleaseSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock){ LONG_PTR CurrentValue, NewValue; PRTLP_SRWLOCK_WAITBLOCK WaitBlock; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (!(CurrentValue & RTL_SRWLOCK_OWNED)) //此时如果不处于拥有状态,抛出异常 { RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); } if (!(CurrentValue & RTL_SRWLOCK_SHARED)) //必须不处于读取状态 { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { WaitBlock = RtlpAcquireWaitBlockLock(SRWLock); if (WaitBlock != NULL) { RtlpReleaseWaitBlockLockExclusive(SRWLock, WaitBlock); 如果有等待独占的线程,调用该函数,将指针指向的链表头传给它 break; } } else { ASSERT(!(CurrentValue & ~RTL_SRWLOCK_OWNED)); NewValue = 0; if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) //如果没有等待独占的,指针置为0 { break; } } } else { RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); 如果处于读取状态 抛出异常 } YieldProcessor(); }}
RtlpReleaseWaitBlockLockExclusive:
NTAPIRtlpReleaseWaitBlockLockExclusive(IN OUT PRTL_SRWLOCK SRWLock, IN PRTLP_SRWLOCK_WAITBLOCK FirstWaitBlock){ PRTLP_SRWLOCK_WAITBLOCK Next; LONG_PTR NewValue; Next = FirstWaitBlock->Next; if (Next != NULL) 如果还有其他在等待的线程 { NewValue = (LONG_PTR)Next | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; if (!FirstWaitBlock->Exclusive) 如果第一个等待的线程是读取线程 { ASSERT(Next->Exclusive); Next->SharedCount = FirstWaitBlock->SharedCount; //复制读取线程计数 NewValue |= RTL_SRWLOCK_SHARED; } Next->Last = FirstWaitBlock->Last; 将Last指向链尾的对象 } else //如果只有一个在等待的线程 { if (FirstWaitBlock->Exclusive) //如果该线程是生产线程,简单设置一个拥有位 NewValue = RTL_SRWLOCK_OWNED; else { //如果该线程是消费线程,设置高位为线程计数,低位为标志 拥有 和 读取 ASSERT(FirstWaitBlock->SharedCount > 0); NewValue = ((LONG_PTR)FirstWaitBlock->SharedCount << RTL_SRWLOCK_BITS) | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_OWNED; } } (void)InterlockedExchangePointer(&SRWLock->Ptr, (PVOID)NewValue); 设置新的指针 if (FirstWaitBlock->Exclusive) { (void)InterlockedOr(&FirstWaitBlock->Wake, 将第一个等待的线程唤醒,如果它是生产线程的话。 TRUE); } else //如果第一个等待唤醒的是一个消费线程的等待块,根据SharedWakeChain链表, 唤醒每一个等待的消费线程。 { PRTLP_SRWLOCK_SHARED_WAKE WakeChain, NextWake; WakeChain = FirstWaitBlock->SharedWakeChain; do { NextWake = WakeChain->Next; (void)InterlockedOr((PLONG)&WakeChain->Wake, TRUE); WakeChain = NextWake; } while (WakeChain != NULL); }}
- Windows核心编程笔记(八)用户模式下的线程同步 SRWLock剖析
- Windows核心编程笔记(八)用户模式下的线程同步
- Windows核心编程笔记 用户模式下的线程同步
- windows 核心编程(用户模式下的线程同步)
- Windows核心编程笔记(6)----用户模式下的线程同步
- Windows核心编程:用户模式下的线程同步
- Windows核心编程:用户模式下的线程同步
- windows核心编程-8.用户模式下的线程同步
- windows核心编程-用户模式下的线程同步1
- windows核心编程用户模式下的线程同步
- windows核心编程---用户模式下的线程同步
- Windows核心编程:(五)用户模式下线程同步
- Windows核心编程学习笔记(17)--用户模式下的线程同步
- 【Windows核心编程学习笔记】用户模式下的线程同步之二---关键段(critical section)
- Windows核心编程(七)用户模式下的线程同步
- Windows核心编程 线程基础 线程调度、优先级、关联性 用户模式下的线程同步
- Windows核心编程学习笔记 第二部分 完成编程任务 第8章 用户模式下的线程同步
- 《Windows核心编程》读书笔记八 用户模式下的内核同步
- CRUD是什么
- 【欧拉回路/通路】 nyoj42 一笔画问题(无向图) && poj1386Play on Words(有向图)
- Web.Config中文件上传限制
- 单选按钮的显示与隐藏列项
- android之service的startService和bindService的区别
- Windows核心编程笔记(八)用户模式下的线程同步 SRWLock剖析
- win10cmd切换目录
- 如何估算网站日承受最大访问PV
- 关灯游戏 Lights out (一)(极速求解)
- code(vs)1039 数的划分
- 【BZOJ 2809】[Apio2012]dispatching 可并堆
- thinkphp5url优化
- LwIP源代码文件目录
- C++——读入和输出优化