當多個線程同時使用相同的資源時,由於是並發執行,不能保證先後順序.所以假如時一個公開變數被幾個線程同時使用會造成該變數值的混亂.
下面來舉個簡單例子.
假如有一個字元陣列變數
char g_charArray[4];
CString szResult;
AfxBeginThread(FunOne,NULL); //FunOne給數組賦值全為S
AfxBeginThread(FunTwo,NULL); //FunTwo也給數組賦值全為B
AfxBeginThread(GetResult,NULL); //得到數組的值,本來期望的值為最後一次賦的值BBBB.但實際結果是不確定的,可能是SBSB,SSBB或者其它
UINT FunOne(LPVOID pParam){ //給數組賦值
for(int i =0; i < 4; i++){
g_charArray[i] = 'S';
Sleep(10);
}
return 0;
}
UINT FunTwo(LPVOID pParam){//得到數組值
for(int i =0; i < 4; i++){
g_charArray[i] = 'B';
Sleep(10);
}
return 0;
}
UINT GetResult(LPVOID pParam){//給數組賦值
szResult = CString(g_charArray);
return 0;
}
Win32 API中臨界區(Critical Section)
為了使公開變數不會被毫無秩序,混亂的更改.我們希望一個線程使用這個變數時其他線程就不能使用,只能等別的線程用完了才用.
於是出現了臨界區這說法,相當於把使用到的變數的記憶體看作一塊地區,當某個線程使用時就進入該地區,使用完了離開.當有一個線程進入該地區時其他線程就只能在外面等.臨界區的使用如下.
臨界區只能用於同一個進程內的線程同步.如果想要多個進程間的線程同步就不能用它了.
CRITICAL_SECTION g_criSection //定義臨界區變數
void MainTestFun{
InitializeCriticalSection(&g_criSection); //使用前必須初始化
char g_charArray[4];
CString szResult;
AfxBeginThread(FunOne,NULL);
AfxBeginThread(FunTwo,NULL);
AfxBeginThread(GetResult,); //此時得到的值總是BBBB或者SSSS,哪一個線程擁有臨界區不確定.只能保證某一個時間只能有一個線程擁有
//DeleteCriticalSection(&g_criSection); 使用完了臨界區就要刪除掉,但是在這裡這樣使用可能會出錯.因為必須保證在刪除時這個臨界區沒有再被使用了.所以如果一個類中,一般把它放解構函式中去調用.
}
//線上程調用的函數的開始調用EnterCriticalSection結束時調用CriticalSection(&g_criSection);
//在中間有用到的所有資源在被使用時其他線程不能使用(不能讀也不能改寫).其它兩個函數FunOne,GetResult按同樣的方法添加這兩行代碼.這裡不再寫出來了.
UINT FunOne(LPVOID pParam){
EnterCriticalSection(&g_criSection); //表示進入臨界區
for(int i =0; i < 4; i++){
g_charArray[i] = 'S';
Sleep(1);
}
LeaveCriticalSection(&g_criSection); //離開臨界區
return 0;
}
MFC中的臨界區類
MFC把上面的操作封裝成一個類CCriticalSection,使用函數Lock與Unlock表示進入和離開臨界區.上鎖和解鎖的說法是更符合我們的習慣思維的.
#include "afxmt.h" //需要添加該標頭檔引用
CCriticalSection g_criSection;
void MainTestFun{
char g_charArray[4];
CString szResult;
AfxBeginThread(FunOne,NULL);
AfxBeginThread(FunTwo,NULL);
AfxBeginThread(GetResult,); //此時得到的值總是BBBB或者SSSS,哪一個線程擁有臨界區不確定.只能保證某一個時間只能有一個線程擁有 }
//其它兩個類也同樣加上這兩行代碼
UINT FunOne(LPVOID pParam){
g_criSection.Lock()//給所有使用的資源上鎖
for(int i =0; i < 4; i++){
g_charArray[i] = 'S';
Sleep(1);
}
g_criSection.UnLock(); //給使用的資源解鎖
return 0;
}
其他線程同步的方法
使線程同步共有4種方法:
1.臨界區(critical section)
2.事件(event)
3.訊號量(Semaphore)
4.互拆量(Mutex)
其中1臨界區是使用者物件,所以沒太多許可權,只能處理同一個進程內的線程同步
而剩下的2,3,4都是核心對象,許可權比較大,可以跨進程使用,因此能夠處理不同進程間的線程同步問題.
其中的Mutext和臨界區適用的情境基本上一樣,只不過Mutext是核心對象,而critical section是使用者物件