採用多線程的好處大家都很熟悉了,可以充分利用系統資源,通過合理調度最大程式上並發執行,但是如果設計不當反而會與初衷相悖,帶來更多的麻煩,本文主要就多線程編程中的“資料競爭”問題做一個歸納和總結,並給出WIN32下部分函數使用說明。
多線程編程中資料競爭是一項關鍵的技術,常用的解決方案有以下四種:臨界區、互斥量、 事件 、 訊號量
臨界區一般不推薦使用,下面主要介紹後面三種。
一、 互斥量 Mutex
學過電腦網路的朋友相信對令牌環網應該不陌生,互斥量的作用就相當於一塊令牌,每個主機都要競爭地去“申請”這張令牌,“獲得”的主機才有許可權在網上發資料包,而“令牌”只有一張,“令牌”的使用權只有噹噹前使用者“釋放”後才能被其它主機“競爭申請”。
互斥量的特點是只有一個,各線程競爭使用,一個線程獲得後,在它釋放前,其它線程只好等待。
1. Win32平台下,互斥量為一個控制代碼,初始化方法如下:
Handle hMutex;
hMutex = CreateMutex(NULL, TRUE, NULL); //建立後當前線程初始佔有互斥量
ReleaseMutex(hMutex); //建立後在主線程中釋放互斥量從而子線程可以申請使用
hMutex = OpenMutex(MUTANT_ALL_ACCESS, TRUE, NULL); //開啟互斥量,並聲明子線程可以繼承互斥量的控制代碼
2. 申請與釋放
WaitForSingleObject(hMutex, DWORD dwTimeOut);
/* do the task; */
ReleaseMutex(hMutex);
例如,可設逾時為100毫秒,如下所示:
if (WAIT_TIMEOUT == WaitForSingleObject(hrecvEven, 100)) {
/* do something; */ }
else {
/* do the task; */
ReleaseMutex(hMutex); }
二、事件 Event
事件常被大家比喻成為一個“紅綠燈”,可以線上程中方便地把燈設為“紅”從而阻塞部分請求該資源的線程,或設為“綠”,開啟所有因這部分資源而阻塞的線程。
最常用的一個情境就是網路緩衝區,當資料處理線程從網路緩衝區中提取資料包進行處理時,首先要做的操作就是判斷緩衝區是否為空白,如非空則提取並處理,如為空白則迴圈檢測,這種實現會大大地把CPU資源浪費在迴圈檢測,最好的方法是採用互斥事件,每次都用WaitForSingleObject去申請資源,如果為“紅”時則線程阻塞,而寫入緩衝區線程將資料寫入時執行SetEvent函數,從而在整個進程空間中廣播“綠”燈,這樣處理線程狀況就可以從阻塞變成就緒從而執行操作。
在使用互斥事件時常犯的一個錯誤就是誤把事件當做互斥量在兩個線程中防止資料競爭,如下例所示:
Handle hEvent;
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 實始化訊號量,初始狀態為非訊號通知
SetEvent(hEvent ); //訊號通知
ThreadA
{
WaitForSingleObject(hEvent);
ResetEvent(hEvent);
/* do the task; */
SetEvent(hEvnet);
}
ThreadB
{
WaitForSingleObject(hEvent);
ResetEvent(hEvent);
/* do the task; */
SetEvent(hEvnet);
}
上例中運行時會如何意想不到的事情,線程A執行時明明申請到了互斥事件並把燈設為“紅”,但線程B還是可以申請到互斥事件並執行,原因是這樣的,在A WaitForSingleObject成功後,在A執行ResetEvent之前,B可能搶佔了CPU並執行了WaitForSingleObject,從而B也有權利執行ResetEvent,這樣A、B都有權執行,這種情況下,等於有兩個人都可以控制“紅綠燈”從而導致“交通混亂”,最好的辦法是在所有線程中只有一個線程可以開、關燈,或對互斥事件進行互斥量保護,防止資料競爭。
二、訊號量 Semaphore
訊號量的意義可以理解為代表一種資源的個數,比如是排隊系統中座位的數量,所有它的值是大於或等於1的,等於1時訊號量則退化為互斥量 Mutex。