用户方式中的线程同步
一、原子访问:互锁的函数家族
原子访问:线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源。
使一个变量的值递增,返回初始值:
LONG InterlockedExchangeAdd(PLONG plAddend, LONG lIncrement);
注意:第二个参数传递一个负值将减去一个值。
用第二个参数中传递的值来取代第一个参数中传递的当前值, 返回原始值:
LONG InterlockedExchange(PLONG plTarget, LONG lValue);
PVOID InterlockedExchangePointer(PVOID* ppvTarget, PVOID pvValue);
执行一个原子测试和设置操作:
PVOID InterlockedCompareExchange(PLONG plDestination, LONG lExchang, LONG lComparand);
PVOID InterlockedCompareExchangePointer(PVOID *ppvDestination, PVOID pvExchange, PVOID pvComparand);
较老的函数,只能递增(或递减)1:
LONG InterlockedIncrement(PLONG plAddend);
LONG InterlockedDecrement(PLONG plAddend);
二、高速缓存行
CPU的高速缓存行:当一个CPU从内存读取一个字节时,它不只是取出一个字节,而是取出足够的字节来填入高速缓存行。高速缓存行由32或64个字节组成,并且始终在第32个字节或64个字节的边界上对齐。
最好是始终都让单个线程来访问数据(函数参数和局部变量是确保这一点的最好方法)或者始终让单个CPU访问这些数据(使用线程亲缘性)。
三、关键代码段
指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。
关键代码段数据结构:CRITICAL_SECTION
进入关键代码段:void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
EnterCriticalSection负责进行下列测试:
l 如果没有线程访问该资源:更新成员变量,指明调用线程已被赋予访问权并立即返回,使该线程能够继续运行(访问该资源)。
l 如果成员变量指明调用线程已经被赋予对资源的访问权:更新变量,指明调用线程多少次被赋予访问权并立即返回,使该线程能够继续运行。
l 如果成员变量指明其它线程已被赋予对资源的访问权:调用线程置于等待状态。等待的线程不会占用任何CPU时间。系统能够记住该线程想要访问该资源并自动更新成员变量,一旦目前访问该资源的线程访问结束,该线程就出于可调度的状态。
注意:EnterCriticalSection的调用最终会超时,导致产生一个异常条件。超时时间值在注册表中设置:
HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Session Manager
离开关键代码段:void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
关键代码段的优点是它们使用非常容易,在内部使用互锁函数,这样能够迅速运行。关键代码段的主要缺点是无法用它们对多个进程中的各个线程进行同步。
必须在任何线程试图访问被保护的资源之前初始化CRITICAL_SECTION结构:
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
必须在任何线程不再试图访问共享资源时,清除该CRITICAL_SECTION结构:
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);
该函数决不允许调用线程进入等待状态,返回值指明调用线程是否能够获得对资源的访问权。该资源被另一个线程访问返回FALSE,其它均返回TRUE。
四、关键代码段与循环锁
调用线程被置于等待状态,意味着该线程必须从用户方式转入内核方式(大约1000个CPU周期)。使用循环锁进行循环,以便设法多次取得该资源。只有当每次试图取得该资源都失败时,该线程才转入内核方式,以便进入等待状态。
BOOL InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs, DWORD dwSpinCount);
在单CPU计算机上dwSpinCount被忽略,始终被置为0.
DWORD SetCriticalSectionSpinCount(PCRITICAL_SECTION pcs, DWORD dwSpinCount);
五、非常有用的提示和技巧
每个共享资源使用一个CRITICAL_SECTION变量
同时访问多个资源时,必须始终按照完全相同的顺序请求资源的访问。但调用LeaveCriticalSection函数时,按照什么顺序访问资源没有关系,因为该函数绝不会使线程进入等待状态。
不要长时间运行关键代码段