【免杀前置课——Windows编程】十二、线程同步——一文讲懂什么是线程同步、原子操作函数、临界区、互斥体(激发态与非激发态区别)

线程同步

多线程运行同一操作对象问题

#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函数,将互斥体释放,即为解锁,此时互斥体不被任何对象拥有,被设置为激发态,会在等待它的线程中随机选择一个重复前面的步骤。

互斥体优点:

互斥体是内核对象,可以跨进程访问
互斥体比较安全,一旦拥有者崩溃,互斥体会立即处于激发态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

webfker from 0 to 1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值