采用多線程的好處大家都很熟悉了,可以充分利用系統資源,通過合理調度最大程序上並發執行,但是如果設計不當反而會與初衷相悖,帶來更多的麻煩,本文主要就多線程編程中的“數據競爭”問題做一個歸納和總結,並給出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。
作者:jia0511