原子操作
首先来一段代码
#include <stdio.h>
#include <Windows.h>
#include <process.h>
int gNum = 0;
unsigned int __stdcall ThreadRunOne(void* lParam)
{
gNum += 1;
return 0;
}
unsigned int __stdcall ThreadRunTwo(void* lParam)
{
gNum += 1;
return 0;
}
int main()
{
HANDLE hThreads[2] = { INVALID_HANDLE_VALUE };
hThreads[0] = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, ThreadRunOne, nullptr, 0, nullptr));
hThreads[1] = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, ThreadRunTwo, hThreads[0], 0, nullptr));
WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);
printf("%d", gNum);
for each (auto var in hThreads)
{
CloseHandle(var);
}
return 0;
}
多运行几次,会发现gNum为1.那么为什么会出现这种状况呢?
其实是2个线程”同时”访问导致,在汇编的层面,一个线程只做了访问gNum的值,然后CPU就把时间片分给了另一个线程,另一个线程去访问gNum,后面再做+1操作去,导致gNum为1
为了避免这种状况,我们应该保证此次操作是不被打断的,即我对这个变量进行操作的时候,其它不能对他进行操作,只有当我完成的时候,其它的才能去操作
在Windows下有,InterlockExchangeAdd函数可以符合上程序需求,熟称原子操作.在对gNum加1的时候,会上一个锁,其它线程就没有机会对gNum操作,从而不会打断前者
旋转锁
BOOL bUsing = FALSE;
//线程函数
unsigned int __stdcall ThreadRunTwo(void* lParam)
{
//当bUsing为FALSE时,才会运行以下代码,相当于对下面的代码加上了锁
while (InterlockedExchange((long*)&bUsing, TRUE) == TRUE)
Sleep(0);
gNum += 1;
return 0;
}
旋转锁有一定程度的对代码进行加锁,但是会大大消耗资源,效率很低
临界区
使用临界区进行线程同步,就要使用CRITICAL_SECTION这个结构体,初始化临界区
void WINAPI InitializeCriticalSection( _Out_ LPCRITICAL_SECTION lpCriticalSection );
进入临界区
void WINAPI EnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
离开临界区
void WINAPI LeaveCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
删除临界区
void WINAPI DeleteCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
例子
#include <stdio.h> #include <Windows.h> #include <process.h> CRITICAL_SECTION gCs; unsigned int __stdcall ThreadRunOne(void* lParam) { EnterCriticalSection(&gCs); static int nThreadId = 0; nThreadId++; printf("Thread:%d\r\n", nThreadId); LeaveCriticalSection(&gCs); return 0; } int main() { HANDLE hThreads[10] = { INVALID_HANDLE_VALUE }; InitializeCriticalSection(&gCs); for (int i = 0; i < 10; i++) { hThreads[i] = (HANDLE)_beginthreadex(nullptr, 0, ThreadRunOne, nullptr, 0, nullptr); } WaitForMultipleObjects(10, hThreads, TRUE, INFINITE); for (int i = 0; i < 10; i++) { CloseHandle(hThreads[i]); } DeleteCriticalSection(&gCs); return 0; }
补充
- 上述代码在EnterCriticalSection处,如果先前有线程进入了,但还没退出,那么其它线程在执行到这时,就会阻塞
此时可用TryEnterCriticalSection函数,尝试进入.如果成功进入就执行进入后的代码,不能就执行其它代码
BOOL WINAPI TryEnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
slim锁
使用slim锁进行线程同步,就要使用SRWLOCK结构体,初始化slim锁
VOID WINAPI InitializeSRWLock( _Out_ PSRWLOCK SRWLock );
以独占的方式进入slim锁
VOID WINAPI AcquireSRWLockExclusive( _Inout_ PSRWLOCK SRWLock );
以独占方式退出slim锁
VOID WINAPI ReleaseSRWLockExclusive( _Inout_ PSRWLOCK SRWLock );
以共享方式进入slim锁
VOID WINAPI AcquireSRWLockShared( _Inout_ PSRWLOCK SRWLock );
补充
- 以独占的方式访问锁必须以独占的方式退出锁,同样共享的方式也是一样
- 此锁不能在递归中使用,比如在独占方式进入锁和独占方式退出锁中不能穿叉其它以独占方式或共享方式进入锁
- 独占方式进入锁,容易阻塞.当该锁是正在以共享的方式使用时,一个线程想要独占的方式进入锁,那么要等到这些以共享方式的线程全部退出该锁后,该线程才能独占这个锁,所以此时独占线程会阻塞掉