天天看点

C++系列 --- 线程同步临界区原理

1、临界区对象

临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,Windows内部使用这个结构记录一些同步信息,确保在同一段时间只有一个线程访问数据段中的数据。

临界区对象相关函数:

// 初始化临界区对象资源
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// 进入临界区
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// 离开临界区
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// 删除临界区对象资源
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
           

函数参数lpCriticalSection为指向临界区源的对象

2、临界区对象实例 

#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;

int g_Cnt = 0; // 计数器
CRITICAL_SECTION g_cs;  // 临界区对象
BOOL bFlag = FALSE;// 判断是否创建辅助线程

UINT _stdcall ThreadFunc(LPVOID lpParam);


int main()
{
	UINT uId[2];

	HANDLE h[2];

	::InitializeCriticalSection(&g_cs);  // 初始化临界区资源

	h[0] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId[0]);
	h[1] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId[1]);

	// 标志创建了辅助线程
	bFlag = TRUE;  
	// 主线程休息1000ms,让出CPU的使用权,让我们的辅助线程有运行的机会
	Sleep(1000);

	// 主线程运行
	bFlag = FALSE;

	::WaitForMultipleObjects(2, h, TRUE, INFINITE);

	// 关闭句柄
	CloseHandle(h[0]);
	CloseHandle(h[1]);

	::DeleteCriticalSection(&g_cs);  // 清除临界区资源

	printf("g_Cnt = %d\n", g_Cnt);

	system("pause");
	return 0;
}

UINT _stdcall ThreadFunc(LPVOID lpParam)
{
	if (bFlag)
	{
		::EnterCriticalSection(&g_cs); 
		for (int i =0;i< 10000;i++)
			g_Cnt++;
		::LeaveCriticalSection(&g_cs);
		
	}

	return 0;
}
           

临界区对象能很好的保护共享数据,但是它不能够用于进程之间资源的锁定,因为它不是内核对象,如果要在进程间维持线程的同步,可以使用事件内核对象。 

3、互锁函数

互锁函数为同步访问多线程共享变量提供了一个简单的机制。

如果变量在共享内存,不同进程的线程也可以使用此机制。

互锁函数:

// 原子自加操作
InterlockedIncrement


// 原子自减操作
InterlockedDecrement


InterlockedExchangeAdd


InterlockedExchangePointer
           

4、实例 

#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;

int g_Cnt = 0; // 计数器
BOOL bFlag = FALSE;// 判断是否创建辅助线程

UINT _stdcall ThreadFunc(LPVOID lpParam);

int main()
{
	UINT uId[2];
	HANDLE h[2];

	h[0] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId[0]);
	h[1] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId[1]);

	// 标志创建了辅助线程
	bFlag = TRUE;  
	// 主线程休息1000ms,让出CPU的使用权,让我们的辅助线程有运行的机会
	Sleep(1000);

	// 主线程运行
	bFlag = FALSE;

	::WaitForMultipleObjects(2, h, TRUE, INFINITE);

	// 关闭句柄
	CloseHandle(h[0]);
	CloseHandle(h[1]);

	printf("g_Cnt = %d\n", g_Cnt);

	system("pause");
	return 0;
}

UINT _stdcall ThreadFunc(LPVOID lpParam)
{
	if (bFlag)
	{
		for (int i = 0; i < 10000; i++)
			// 原子自加操作,不会被中断
			InterlockedIncrement((long *)&g_Cnt);
	}

	return 0;
}
           

通过本节的学习,我们知道了如何控制关键的代码段不被系统打断,可以通过临界区或者互锁函数来实现关键代码段不被系统打断,可以保证数据得出正确的结果!