企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
在多线程环境下,线程同步是不可避免的话题。Windows环境下的线程同步分为:用户态同步 与 内核态同步。 下面我们先了解用户态同步的一些方法。 - 使用Interlocked系列函数。简单易用的函数。 - 关键段。用来对关键资源实现独享访问。 - Slim读写锁。灵活的进行写独享读共享操作。 - 条件变量。当线程要进行较为复杂的条件进行同步时,可以实现。 Interlocked系列函数。 Windows提供了Interlocked系列函数,用来原子的对数据进行增减或交换。如自旋转锁,就可以通过InterlockedExchange函数实现。 ~~~ BOOL g_fResourceInUse = FALSE; void Func1() { // InterlockedExchange会一直原子设置g_fResourceInUse为TRUE,同时返回g_fResourceInUse // 上一次的值。当g_fResourceInUse为初始值FALSE或被另一线程设置为FALSE后,该循环才会结束。 // 以此自循环(自旋锁)方式来实现对资源的独占访问。 while (InterlockedExchange(&g_fResourceInUse,TRUE) == TRUE) Sleep(0); // Access the resource, do something ... // Do not need the resource anymore, release it InterlockedExchange(&g_fResourceInUse,FALSE) } ~~~ 注意,这种循环方式会占用CPU大量时间,不建议在单CPU机器上运行。(可以用关键段代替或用C++11标准中的atom系列函数代替) 关键段 上面使用自旋锁的方式进行同步显然是低效的。因为等待线程依然处于可调度状态,仍然会占用CPU时间。Windows提供了一系列函数,让线程同步。这一系列函数保证了在线程获得想要的资源之前,不被CPU调度,直到其要求的资源可被线程访问为止。关键段就是其一,其实关键段的实现是通过事件内核对象的。 运用关键段五个步骤: 1、声明一个可以被多个线程访问到其地址的关键段变量。 2、在使用关键段前,调用InitializeCriticalSection函数初始化关键段。 2、在进入资源前调用EnterCriticalSection,请求进入关键段(若进入不了,则线程等待) 3、在离开资源时,调用LeaveCriticalSection,离开关键段。 若确定了关键段已经不被任何线程再使用,则要销毁关键段对象。 4、在不再使用关键段时,调用DeleteCriticalSection销毁关键段。 ~~~ CRITICAL_SECTION g_cs; int g_sum = 0; //初始化关键段,注意不要多次初始化,否则后果是未定义的 <pre name="code" class="cpp">InitializeCriticalSection(&g_cs); ~~~ ~~~ void ThreadFunc1() {       EnterCriticalSection(&g_cs);    g_sum++;    LeaveCriticalSection(&g_cs); } void ThreadFunc2() {       EnterCriticalSection(&g_cs);    g_sum++;    LeaveCriticalSection(&g_cs); } ... ... // 不再使用critical section,显示销毁 DeleteCriticalSection(&g_cs); ~~~ 关键段最容易忘记 ~~~ LeaveCriticalSection ~~~ ,这时候可以用RAII技巧来进行简单的封装。 关于关键段的细节 1、若一个线程已经成功进入关键段,则可以多次调用EnterCriticalSection,相应的,要调用多次LeaveCriticalSection来离开临界区。 2、对于跨进程的线程同步,可以使用mutex对象。 可以使用TryEnterCriticalSection进入关键段,他不会使线程进入等待,而是返回布尔值表示是否获得了关键段。对于返回TRUE,需要调用LeaveCriticalSection。 关键段与旋转锁 当线程由于得不到关键段而进入等待状态时,会进行用户态和内核态切换,这会占用大量的CPU时间。在多处理器的环境下,可能的一种情况是,用户/内核态的切换还未结束,占用关键段的线程可能已经释放了关键段。 在多处理器的情况下,可以使用 [**InitializeCriticalSectionAndSpinCount**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683476%28v=vs.85%29.aspx) 函数来初始化关键段。其函数原型如下 ~~~ BOOL WINAPI InitializeCriticalSectionAndSpinCount( _Out_ LPCRITICAL_SECTION lpCriticalSection, _In_ DWORD dwSpinCount ); ~~~ 其中参数dwSpinCout用来设置旋转锁循环次数。[**SetCriticalSectionSpinCount**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686197%28v=vs.85%29.aspx) 可以重设自旋转锁次数。 该函数会在进入内核态前,旋转设置的循环次数来获取关键段。若在旋转锁阶段获取关键段,则不会进入内核态。 注意,在单CPU模式下,dwSpinCout是被忽略的,总是为0。因为在单CPU下, ~~~ InitializeCriticalSectionAndSpinCount ~~~ 是没有意义的:CPU在旋转锁阶段被线程占用,其他线程根本没有时机来释放关键段。但我们仍可以这样初始化关键段,以应对未来可能的多CPU环境。 Slim读写锁 一般的,对于线程的同步,读是可以共享的,而写则是互斥的。因此Windows提供了读写锁机制。 与关键段类似,在使用读写锁之前,要调用[**InitializeSRWLock**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683483%28v=vs.85%29.aspx)函数初始化读写锁。 利用读写锁要分清读者和写者。 读写锁使用步骤 1、声明SRWLOCK对象。 2、用InitializeSRWLock函数初始化SRWLOCK对象。 3.1、对于读者,调用 [**AcquireSRWLockShared**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms681934%28v=vs.85%29.aspx) [**ReleaseSRWLockShared**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685080%28v=vs.85%29.aspx) 以共享的方式获取,释放的读写锁。若该锁没被占用或被其他线程读,则立即获得锁,否则等待。 3.2、对于写者,调用 [**AcquireSRWLockExclusive**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms681930%28v=vs.85%29.aspx) [**ReleaseSRWLockExclusive**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685076%28v=vs.85%29.aspx) 以独占的方式获取,释放的读写锁。若该锁未被占用,则立即获得锁,否则等待。 Slim与关键段的对比 Slim锁与关键段主要有以下两点区别 1、Slim锁不能够递归获取,即当一个线程Acquire并获得Slim锁之后,不能够再次Acquire同一把锁。 2、不存在TryEnter类似函数获取Slim锁。 3、Slim锁不用显示销毁,系统会自动释放。 4、总体上说,Slim锁的效率优于关键段。 多种同步方法的效率对比 ![](https://box.kancloud.cn/82c440c7466c6449356a9b8660ba37f6_617x136.jpg) 条件变量同步 有时候需要线程原子方式释放获得的锁同时阻塞自身,直到某一条件成立为止。这时候可以通过条件变量进行同步。 等待条件变量函数。当条件被满足,线程被唤醒后,会自动得到锁。 [**SleepConditionVariableCS**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686301%28v=vs.85%29.aspx) [**SleepConditionVariableSRW**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686304%28v=vs.85%29.aspx) 唤醒等待条件的线程函数 [**WakeAllConditionVariable**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687076%28v=vs.85%29.aspx) [**WakeConditionVariable**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687080%28v=vs.85%29.aspx)