windows多線程(三) 原子操作


一、分析上一篇程序的現象

我們先從上一篇文章中的最后一個程序開始分析。



#include <stdio.h>
#include <windows.h>

const unsigned int THREAD_NUM = 10;
DWORD WINAPI  ThreadFunc(LPVOID);

int main()
{
	printf("我是主線程, pid = %d\n", GetCurrentThreadId());  //輸出主線程pid
	HANDLE hThread[THREAD_NUM];
	for (int i = 0; i < THREAD_NUM; i++)
	{
		hThread[i] = CreateThread(NULL, 0, ThreadFunc, &i, 0, NULL); // 創建線程
	}
	
	WaitForMultipleObjects(THREAD_NUM,hThread,true, INFINITE);	//一直等待,知道所有子線程全部返回
	return 0;
}

DWORD WINAPI  ThreadFunc(LPVOID p)
{
	int n = *(int*)p;
	Sleep(1000*n);  //第 n 個線程睡眠 n 秒
	printf("我是, pid = %d 的子線程\n", GetCurrentThreadId());   //輸出子線程pid
	printf(" pid = %d 的子線程退出\n\n", GetCurrentThreadId());   //延時10s后輸出

	return 0;
}

看程序的輸出:

按照正常情況來看應該是每一行輸出兩列,但是中間有一行多出了一列,看圖中圈出來的地方,pid = 208 的線程輸出線程pid后並沒有馬上退出,而是等到了最后才退出。(可能每次運行的情況不一樣,這里只說明這一種情況),這是為什么的。 這里涉及到了線程調度的問題, 說明pid = 208 的線程輸出線程pid后操作系統進行了線程調度,cpu資源被其它線程搶占,這個線程直到最后才又重新分配到cpu資源,重新往下執行。

二、原子操作

這里明明是要寫原子操作,但是到目前為止,並沒有任何地方提及什么是原子操作,不要着急,接下來就慢慢來說。那么什么是原子操作呢?一個操作如果能夠不受中斷地完成,我們稱之為原子操作

我們來看這個程序


#include <stdio.h>
#include <windows.h>

const unsigned int THREAD_NUM = 50;
unsigned int g_Count = 0;
DWORD WINAPI  ThreadFunc(LPVOID);


int main()
{
	HANDLE hThread[THREAD_NUM];
	for (int i = 0; i < THREAD_NUM; i++)
	{
		hThread[i] = CreateThread(NULL, 0, ThreadFunc, 0, 0, NULL); // 創建線程
	}
	WaitForMultipleObjects(THREAD_NUM, hThread, true, INFINITE);	//一直等待,直到所有子線程全部返回
	printf(" 總共 %d 個線程給 g_Count 的值加一,現在 g_Count = %d\n", THREAD_NUM, g_Count);
	return 0;
}

DWORD WINAPI  ThreadFunc(LPVOID p)
{
	Sleep(50);
	g_Count++;
	Sleep(50);
	
	return 0;
}



有一個全局變量 g_Count ,每個線程給這個全局變量加一,照這么來看最后應該輸出 50 ,我們看一下程序的輸出(每次都可能不一樣的結果)

為什么會這樣呢??? 明明有 50 個線程都給 g_Count 加一了,為什么輸出 46,根源在於 g_Count++; 這條語句上,這里就只有一條c++語句,按理說不應該有問題,其實不然,現在,在這里打下斷點,開始調試,打開反匯編窗口(Vs編譯器快捷鍵 Alt+8),如下圖

可以看到,這一條c++語句,被分成了三條匯編語句,先是把 g_Count 的值給寄存器 eax,然后寄存器 eax 的值加一,再把 eax 的值給 g_Count ,這樣就完成一次 g_Count++ 操作。出問題的原因就在於,在這幾條匯編語句執行的過程中發送了線程切換,比如,A線程剛執行完 add eax,1 還沒有把 eax的值給 g_Count,這時B線程開始執行,把 g_Count 原先的值又存入 eax,這就修改了 eax 中A線程計算好的值。

因此在多線程環境中對一個變量進行讀寫時,我們需要有一種方法能夠保證對一個值的遞增操作是原子操作——即這個操作不可以被打斷性,一個線程在執行原子操作時,其它線程必須等待它完成之后才能開始執行該原子操作。Windows系統為我們提供了一些以Interlocked開頭的函數來完成這一任務。這里只是介紹原子操作的概念,這和線程同步息息相關,但是這些以 以Interlocked 開頭的函數我們基本不用,就不一一介紹了,感興趣的可以自己去了解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM