线程同步
- 线程同步
- 多线程运行同一操作对象问题
- 解决方案A:原子操作函数
- 解决方案B:临界区
- 解决方案C:互斥体
- 激发态与非激发态
- 互斥体优点:
线程同步
多线程运行同一操作对象问题
#include<iostream>
#include<Windows.h>
LONG g_count = 0;
DWORD WINAPI myThreadProc1(
_In_ LPVOID lpParameter
)
{
for (size_t i = 0; i < 100000; i++)
{
g_count++;
}
return 0;
}
DWORD WINAPI myThreadProc2(
_In_ LPVOID lpParameter
)
{
for (size_t i = 0; i < 100000; i++)
{
g_count++;
}
return 0;
}
int main()
{
HANDLE hThread1 = CreateThread(0, 0, myThreadProc1, 0, 0,0);
HANDLE hThread2 = CreateThread(0, 0, myThreadProc2, 0, 0,0);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -2);
printf("%d\n", g_count);
CloseHandle(hThread1);
CloseHandle(hThread2);
return 0;
}
以上是一个双线程同步对g_count进行自增的程序,两次循环都是100000,那么正常结果应该是200000,可运行完后我们会发现,结果并不是这样。只有十万多些,我们查看反汇编。
我们可以发现一个++操作实现需要多个步骤,而线程是并发的,也就是同时进行,但是调用的变量都是一个,所以有可能一个++操作没有执行完就被拉到另一个线程中调用,导致++操作未完整完成,这也就是为什么数据不到200000。
那我们有没有办法解决呢?
用原子操作函数将一个操作多个步骤编程无法分开,即仅我线程完成该操作后才可被其他线程调用。
解决方案A:原子操作函数
多个线程访问相同资源的时候会产生冲突
原子操作函数interlockedIncrement ()
自增。
解决方案B:临界区
原子操作仅仅能够解决某一个变量的问题,只能使得一个整数型数据做简单数据数据运算的时候是原子的,但是大部分时候我们想要的是一整段代码是原子操作,使用临界区就能解决这个问题。
进入临界区
EnterCriticalSection
离开临界区
LeaveCriticalSection
在使用临界区前,需要调用InitializeCriticalSection.初始化一个临界区。使用完后需要调用DeleteCriticalSection_销毁。
解决方案C:互斥体
激发态与非激发态
互斥体有两个状态:激发态、非激发态
。
它有一个概念,叫做线程拥有权,与临界区类似。
当一个互斥体没有被任何一个线程拥有时,它处于激发态,也可以说锁是打开的。
当一个线程调用了WaitForSingleObject,函数会立刻返回,并将互斥体设置为非激发态,互斥体被锁住,该线程获得拥有权。
其它线程调用WaitForSingleObject函数的线程无法获得拥有权,只能一直等待互斥体,他们全部被阻塞。
当线程A调用ReleaseMutex函数
,将互斥体释放,即为解锁,此时互斥体不被任何对象拥有,被设置为激发态,会在等待它的线程中随机选择一个重复前面的步骤。
互斥体优点:
互斥体是内核对象,可以跨进程访问。
互斥体比较安全,一旦拥有者崩溃,互斥体会立即处于激发态。