Ring3层线程同步总结
系统中所有的线程必须访问系统资源,比如堆,串口,文件,窗口以及无数其他资源。如果一个线程独占了对某个资源的访问,那么其他线程就无法完成他们的工作。另一方面,我们页不能让任何线程在任何时刻都能访问任何资源。设想有一个线程正在写入一块内存,而同时另一个线程正在从同一块内存中读取数据,那么这个数据将变得乱七八糟,毫无用处。此时就需要线程同步。
线程同步常用的方法:
1.原子访问(Interlocked系列函数):
必须保证传给这些函数的变量地址是经过对齐的,否则这些函数可能会失败。
优点:执行速度极快,不需要在用户模式和内核模式之间进行切换。
缺点:只能对一个数进行操作。
LONG InterlockedIncrement(PLONG plAddend);//plAddend增加1
LONG InterlockedDecrement(PLONG plAddend);//减小1
LONG InterlockedExchange(PLONG volatile plTarget,LONG lValue);//32位,用value替换Target,返回之前的值
LONGLONG InterlockedExchange64(PLONG volatile plTarget,LONGLONG lValue);//64位
PVOID InterlockedExchangePointer(PVOID* volatile ppvTarget,PVOID pvValue);//x86下是32位,x64下是64位
LONG InterlockedExchangeAdd(PLONG volatile plAddend,LONG lIncrement);//Addend增加Increment
LONGLONG InterlockedExchangeAdd64(PLONGLONG volatile pllAddend,LONGLONG llIncrement);
PLONG INterlockedCompareExchange(PLONG plDestination,LONG lExchange,LONG lComparand);//函数会将Destination当前值与参数Comparand的值进行比较,如果相等则替换为Comparand的值,返回初始值
LONG InterlockedCompareExchangePointer(PVOID* ppvDestination,PVOID pvExchange,PVOID pvComparand);
volatile关键字:告诉编译器这个变量可能会被应用程序之外的其他东西修改,比如操作系统、硬件或者一个并发执行的线程。确切的说,volatile限定符告诉编译器不要对这个变量进行任何形式的优化,而是始终从变量在内存中的位置读取变量的值。
2.关键段(Critical Section):是一段代码,它在执行之前需要独占对一些共享资源的访问权。
优点:容易使用,内部运用Interlocked函数执行速度很快。
缺点:无法在多个进程之间对线程进行同步(其实Ring3的同步方式都无法在多个进程之间不同线程同步)
使用方法:
CRITICAL_SECTION g_cs;//定义为全局
//使用之前先初始化
InitializeCriticalSection(&g_cs);
EnterCriticalSection(&g_cs); .......do Some Things..... LeaveCriticalSection(&g_cs);
//使用完清理cs结构
DeleteCriticalSection(&g_cs);
任何需要访问共享资源的代码,都必须包含在EnterCriticalSection和LeaveCriticalSection之间。
如果共享资源在使用中,则EnterCriticalSection就会使线程处于等待状态,可以使用下面函数,这个函数会判断资源是否在使用,如果使用中则直接返回,不会将线程切换到等待状态。
BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);
3.Slim读/写锁:读写锁目的和关键段相同,对一个资源进行保护不让其他线程访问它。但是读写锁允许我们区分那些想要读取资源的线程和写入资源的线程。
void InitializeSRWLock(PSRWLOCK SRWLock);//使用之前初始化读写锁 void AcquireSRWLockExclusive(PSRWLOCK SRWLock);//写入者线程获取锁 void ReleaseSRWLockExclusive(PSRWLOCK SRWLock);//写入者线程释放锁 //读取者线程 void AcquireSRWLockShared(PSRWLOCK SRWLock); void ReleaseSRWLockShared(PSRWLOCK SRWLock); //不需要删除或者销毁SRWLOCK的函数,系统会自动执行清理工作
与关键段的区别:1.读写锁不存在TryEnter*的函数,如果锁已经被占用,那么获取锁就会导致线程等待;
2.不能递归获得SRWLOCK,也就是说,一个线程不能为了多次写入资源而多次锁定资源,然后在多次调用ReleaseSRWLock*来释放对资源的锁定。
补充知识:
高速缓存行:当CPU从内存中读取一个字节的时候,它并不只是从内存中取回一个字节,而是取回一个高速缓存行。高速缓存行可能包含32字节,64字节甚至是128字节(取决于CPU)。在多CPU的机器上当一个CPU修改了高速缓存行的一个字节时,机器中的其他CPU会受到通知,并使自己的高速缓存行作废。
加入条件变量的锁:
BOOL SleepConditionVariableCS(
PCONDITION_VARIABLE pConditionVariable,
PCRITICAL_SECTION pCriticalSection,
DWORD dwMilliseconds
);
BOOL SleepConditionVariableSRW(
PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWORD dwMilliseconds,
ULONG Flags);
VOID WakeConditionVariable(
PCONDITION_VARIABLE ConditionVariable);
VOID WakeAllConditionVariable(
PCONDITION_VARIABLE ConditionVariable);