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

线程同步

  • 线程同步
    • 多线程运行同一操作对象问题
    • 解决方案A:原子操作函数
    • 解决方案B:临界区
    • 解决方案C:互斥体
      • 激发态与非激发态
      • 互斥体优点:

线程同步

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

#include
#include

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,可运行完后我们会发现,结果并不是这样。只有十万多些,我们查看反汇编。
【免杀前置课——Windows编程】十二、线程同步——一文讲懂什么是线程同步、原子操作函数、临界区、互斥体(激发态与非激发态区别)_第1张图片
我们可以发现一个++操作实现需要多个步骤而线程是并发的,也就是同时进行,但是调用的变量都是一个,所以有可能一个++操作没有执行完就被拉到另一个线程中调用,导致++操作未完整完成,这也就是为什么数据不到200000。
那我们有没有办法解决呢?
用原子操作函数将一个操作多个步骤编程无法分开,即仅我线程完成该操作后才可被其他线程调用

解决方案A:原子操作函数

多个线程访问相同资源的时候会产生冲突
原子操作函数interlockedIncrement ()自增。

解决方案B:临界区

原子操作仅仅能够解决某一个变量的问题,只能使得一个整数型数据做简单数据数据运算的时候是原子的,但是大部分时候我们想要的是一整段代码是原子操作,使用临界区就能解决这个问题
进入临界区
EnterCriticalSection
离开临界区
LeaveCriticalSection
在使用临界区前,需要调用InitializeCriticalSection.初始化一个临界区使用完后需要调用DeleteCriticalSection_销毁。

解决方案C:互斥体

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

激发态与非激发态

互斥体有两个状态:激发态、非激发态
它有一个概念,叫做线程拥有权,与临界区类似。
当一个互斥体没有被任何一个线程拥有时,它处于激发态,也可以说锁是打开的。
当一个线程调用了WaitForSingleObject,函数会立刻返回,并将互斥体设置为非激发态,互斥体被锁住,该线程获得拥有权。
其它线程调用WaitForSingleObject函数的线程无法获得拥有权,只能一直等待互斥体,他们全部被阻塞。
当线程A调用ReleaseMutex函数,将互斥体释放,即为解锁,此时互斥体不被任何对象拥有,被设置为激发态,会在等待它的线程中随机选择一个重复前面的步骤。

互斥体优点:

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

你可能感兴趣的:(Windows编程,免杀前置课,windows,c++,microsoft)