C++系列 --- 操作系統亂序推進線程指令的本質剖析

C/C++運行期庫 

1、概念

在實際的開發過程中,一般使用C/C++運行期函數_beginthreadex代替CreateThread函數。

_beginthreadex首先申請一些用於線程同步的變量,然後調用CreateThread

需要用#include <process.h>頭文件

同樣,使用_endthreadex代替ExitThread。該函數首先釋放用於線程同步的變量,再調用ExitThread。

2、實例 

#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)
{
	while (bFlag)
	{
		g_Cnt++;
	}

	return 0;
}

3、運行結果

得出來的這個結果不是正確的結果,爲什麼呢?

4、原因分析

我們所有的計算機指定都是亂序推進的,我們在線程開發中, 必須認爲在邏輯上嚴格限定程序指定序列的推進。

g_Cnt++:運行詳解

g_Cnt++是上面三條彙編指令運行的,而且這三條指定有可能會被亂序推進,而且運行過程會被打斷。

假定g_Cnt當前值爲100,g_Cnt被推進eax這個寄存器中,這個時候eax的值變爲100,在線程1中,他會將100與1相加存放在eax中,eax的值變爲101,假定這個時候線程1被打斷了,開始執行線程2,現在g_Cnt的值在線程1中還未被修改,所以g_Cnt的值還是100,從線程2中從g_Cnt的值讀到eax中,所以eax的值從101變爲100了,所以只要運行過中被打斷,g_Cnt的結果是無法預料的。

如果想讓g_Cnt的值圓滿運行完畢,我們有且只有一個方法,這個方法就是請讓操作系統告訴系統這個線程中的指令序列是不可以被打斷的。如果被打斷,則會造成運行的結果不可預料。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章