文章目录
Windows编程——线程篇(二):线程同步
为什么需要同步
资源竞争
假如存在2个线程A、B,和资源T,A需要对int T进行读取操作,B需要进行写,并假设读写操作不是原子操作。那么,当A读取T的值得时候,如果B进行写,则A读取到的值讲是不确定的。而所谓原子操作就是某一操作在完成之前这个资源不会被其他线程所访问。
高速缓存行
所谓高速缓存行,就是指CPU在从内存读取一个字节的时候,并不是读取一个字节,而是取出足够的字节来填入高速缓存行。一般高速缓存行的大小为32或者64字节,当所需要访问的字节存在于高速缓存行的时候,CPU就不必去访问内存总线,从而提高存取效率。但这也会造成资源同步的问题,同时 这也会为什么存在register关键字的原因。
用户空间线程同步
原子访问
线程同步问题在很大程度上与原子访问有关,所谓原子访问,是指线程在访问资源时能够
确保所有其他线程都不在同一时间内访问相同的资源。
unsigned InterlockedExchange(unsigned volatile *Target,_In_ unsigned Value);
unsigned InterlockedExchangeAdd(PLONG plAdded,LONG lIncrement);
PVOID IntergerlockedCompareExchange(PLONG plDestination,LONG lExchange,LONG lComparand);
PVOID IntergerlockedCompareExchangePointer(PVOID *ppvDestination,PVOID pvExchange,PVOID pvComparand);
临界区线程同步
临界区
临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。在线程退出关键代码段之前,系统将不给想要访问相同资源的其他任何线程进行调度。
// 进入临界区
// 1.如果没有其他线程访问该资源,则进入进阶区,以指明调用线程已被赋予访问权限并立即返回
// 2.如果已经有其他线程被赋予访问权限,那么将进行等待,等待不会浪费CPU资源,一旦目前访问该资源的线程调用leave函数,系统将通知所有等待中的线程对该资源进行竞争
VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
// 离开临界区
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
// 在任何企图进入临界区的线程之前调用初始化函数,否则后果是不可预料的
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
// 删除临界区资源
VOID DeleteCriticalSection(PCRITICAL_SECION &pcs);
// 测试是否可以进入临界区,可以返回TRUE,否则返回FALSE
BOOL TryEnterCriticalSection(PCRITICAL_SECTION &pcs);
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // from the thread's ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
在使用enter和leave函数之前,先创建一个CRITICAL_SECTION对象(一般是全局变量),并且在使用这个对象之前必须调用初始化函数,然后在临界区前后分别调用enter和leave即可。
临界区与循环锁
当用户阻塞在entercriticalsection的时候,该线程将从用户态切换到内核态,这个时间大概花费1000个CPU周期,这种转换是要付出很大代价的。同时,如果是多核CPU的情况,一个线程请求进入临界区时另一个线程正在该临界区执行代码,那么请求线程将转换到内核态,但是另一线程很可能在转换完成前就已经释放了该临界区,因此这种情况也是非常浪费CPU资源的。这时候可以使用临界区循环锁,如下:
BOOL InititializeCriticalSectionAndSpinCount(