天天看點

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;
}
           

通過本節的學習,我們知道了如何控制關鍵的代碼段不被系統打斷,可以通過臨界區或者互鎖函數來實作關鍵代碼段不被系統打斷,可以保證資料得出正确的結果!