函式原型
函式EnterCriticalSection和LeaveCriticalSection聲明如下:WINBASEAPI
VOID
WINAPI
EnterCriticalSection(
__inout LPCRITICAL_SECTION lpCriticalSection
);
是多執行緒中用來確保同一時刻只有一個執行緒操作被保護的數據的操作函式,相關的多執行緒數據操作函式還有:
InitializeCriticalSection(&cs);//初始化臨界區
EnterCriticalSection(&cs);//進入臨界區
//運算元據
MyMoney*=10;//所有訪問MyMoney變數的程式都需要這樣寫Enter.. Leave...
LeaveCriticalSection(&cs);//離開臨界區
DeleteCriticalSection(&cs);//刪除臨界區
訪問機制
多個執行緒操作相同的數據時,一般是需要按順序訪問的,否則會引導數據錯亂,無法控制數據,變成隨機變數。為解決這個問題,就需要引入互斥變數,讓每個執行緒都按順序地訪問變數。這樣就需要使用EnterCriticalSection和LeaveCriticalSection函式。比如說我們定義了一個共享資源dwTime[100],兩個執行緒ThreadFuncA和ThreadFuncB都對它進行讀寫操作。當我們想要保證 dwTime[100]的操作完整性,即不希望寫到一半的數據被另一個執行緒讀取,那么用CRITICAL_SECTION來進行執行緒同步如下:
第一個執行緒函式:
DWORD WINAPI ThreadFuncA(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作dwTime
...
LeaveCriticalSection(&cs);
return 0;
}
寫出這個函式之後,很多初學者都會錯誤地以為,此時cs對dwTime進行了鎖定操作,dwTime處於cs的保護之中。一個“自然而然”的想法就是——cs和dwTime一一對應上了。
這么想,就大錯特錯了。dwTime並沒有和任何東西對應,它仍然是任何其它執行緒都可以訪問的。如果你像如下的方式來寫第二個執行緒,那么就會有問題:
DWORD WINAPI ThreadFuncB(LPVOID lp)
{
...
// 操作dwTime
...
return 0;
}
當執行緒ThreadFuncA執行了EnterCriticalSection(&cs),並開始操作dwTime[100]的時候,執行緒 ThreadFuncB可能隨時醒過來,也開始操作dwTime[100],這樣,dwTime[100]中的數據就被破壞了。
為了讓CRITICAL_SECTION發揮作用,我們必須在訪問dwTime的任何一個地方都加上 EnterCriticalSection(&cs)和LeaveCriticalSection(&cs)語句。所以,必須按照下面的 方式來寫第二個執行緒函式:
DWORD WINAPI ThreadFuncB(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作dwTime
...
LeaveCriticalSection(&cs);
return 0;
}
這樣,當執行緒ThreadFuncB醒過來時,它遇到的第一個語句是EnterCriticalSection(&cs),這個語句將對cs變數 進行訪問。如果這個時候第一個執行緒仍然在操作dwTime[100],cs變數中包含的值將告訴第二個執行緒,已有其它執行緒占用了cs。因此,第二個執行緒的 EnterCriticalSection(&cs)語句將不會返回,而處於掛起等待狀態。直到第一個執行緒執行了 LeaveCriticalSection(&cs),第二個執行緒的EnterCriticalSection(&cs)語句才會返回, 並且繼續執行下面的操作。
這個過程實際上是通過限制有且只有一個函式進入CriticalSection變數來實現代碼段同步的。簡單地說,對於同一個 CRITICAL_SECTION,當一個執行緒執行了EnterCriticalSection而沒有執行LeaveCriticalSection的時 候,其它任何一個執行緒都無法完全執行EnterCriticalSection而不得不處於等待狀態。
再次強調一次,沒有任何資源被“鎖定”,CRITICAL_SECTION這個東東不是針對於資源的,而是針對於不同執行緒間的代碼段的!我們能夠用它來進 行所謂資源的“鎖定”,其實是因為我們在任何訪問共享資源的地方都加入了EnterCriticalSection和 LeaveCriticalSection語句,使得同一時間只能夠有一個執行緒的代碼段訪問到該共享資源而已(其它想訪問該資源的代碼段不得不等待)。
這就是使用一個CRITICAL_SECTION時的情況。你應該要知道,它並沒有什麼可以同步的資源的“集合”。這個概念不正確。如果是兩個CRITICAL_SECTION,就以此類推。
執行緒鎖的概念函式EnterCriticalSection和LeaveCriticalSection的用法
註:使用結構CRITICAL_SECTION 需加入頭檔案#include “afxmt.h”
定義一個全局的鎖 CRITICAL_SECTION的實例
和一個靜態全局變數
CRITICAL_SECTION cs;//可以理解為鎖定一個資源
static int n_AddValue = 0;//定義一個靜態的全部變數n_AddValue
創建兩個執行緒函式,代碼實現如下:
//第一個執行緒
UINT FirstThread(LPVOID lParam)
{
EnterCriticalSection(&cs);//加鎖 接下來的代碼處理過程中不允許其他執行緒進行操作,除非遇到LeaveCriticalSection
for(int i = 0; i<10; i++){
n_AddValue ++;
cout << "n_AddValue in FirstThread is "< <
}
LeaveCriticalSection(&cs);//解鎖 到EnterCriticalSection之間代碼資源已經釋放了,其他執行緒可以進行操作
return 0;
}
//第二個執行緒
UINT SecondThread(LPVOID lParam)
{
EnterCriticalSection(&cs);//加鎖
for(int i = 0; i<10; i++){
n_AddValue ++;
cout << "n_AddValue in SecondThread is "<<
}
LeaveCriticalSection(&cs);//解鎖
return 0;
}
在主函式添加以下代碼
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// 初始化 MFC 並在失敗時顯示錯誤
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: 更改錯誤代碼以符合您的需要
_tprintf(_T("錯誤: MFC 初始化失敗/n"));
nRetCode = 1;
}
else
{
InitializeCriticalSection(&cs);//初始化結構CRITICAL_SECTION
CWinThread *pFirstThread,*pSecondThread;//存儲函式AfxBeginThread返回的CWinThread指針
pFirstThread = AfxBeginThread(FirstThread,LPVOID(NULL));//啟動第一個執行緒
pSecondThread = AfxBeginThread(SecondThread,LPVOID(NULL));//啟動第二個執行緒
HANDLE hThreadHandle[2];//
hThreadHandle[0] = pFirstThread->m_hThread;
hThreadHandle[1] = pSecondThread->m_hThread;
//等待執行緒返回
WaitForMultipleObjects(2,hThreadHandle,TRUE,INFINITE);
}
return nRetCode;
}
輸出:
n_AddValue in FirstThread is 1
n_AddValue in FirstThread is 2
n_AddValue in FirstThread is 3
n_AddValue in FirstThread is 4
n_AddValue in FirstThread is 5
n_AddValue in FirstThread is 6
n_AddValue in FirstThread is 7
n_AddValue in FirstThread is 8
n_AddValue in FirstThread is 9
n_AddValue in FirstThread is 10
n_AddValue in SecondThread is 11
n_AddValue in SecondThread is 12
n_AddValue in SecondThread is 13
n_AddValue in SecondThread is 14
n_AddValue in SecondThread is 15
n_AddValue in SecondThread is 16
n_AddValue in SecondThread is 17
n_AddValue in SecondThread is 18
n_AddValue in SecondThread is 19
n_AddValue in SecondThread is 20
如果把兩個執行緒函式中的EnterCriticalSection和LeaveCriticalSection位置移到for循環中去,執行緒的執行順序將會改變
輸出也就跟著改變,如:
//第一個執行緒
UINT FirstThread(LPVOID lParam)
{
for(int i = 0; i<10; i++){
EnterCriticalSection(&cs);//加鎖 鎖移到for循環內部里
n_AddValue ++;
cout << "n_AddValue in FirstThread is "< <
LeaveCriticalSection(&cs);//解鎖
}
return 0;
}
//第二個執行緒
UINT SecondThread(LPVOID lParam)
{
for(int i = 0; i<10; i++){
EnterCriticalSection(&cs);//加鎖
n_AddValue ++;
cout << "n_AddValue in SecondThread is "< <
LeaveCriticalSection(&cs);//解鎖
}
return 0;
}
其他代碼不變,輸出的結果如下:
n_AddValue in FirstThread is 1
n_AddValue in SecondThread is 2
n_AddValue in FirstThread is 3
n_AddValue in SecondThread is 4
n_AddValue in FirstThread is 5
n_AddValue in SecondThread is 6
n_AddValue in FirstThread is 7
n_AddValue in SecondThread is 8
n_AddValue in FirstThread is 9
n_AddValue in SecondThread is 10
n_AddValue in FirstThread is 11
n_AddValue in SecondThread is 12
n_AddValue in FirstThread is 13
n_AddValue in SecondThread is 14
n_AddValue in FirstThread is 15
n_AddValue in SecondThread is 16
n_AddValue in FirstThread is 17
n_AddValue in SecondThread is 18
n_AddValue in FirstThread is 19
n_AddValue in SecondThread is 20
個人認為在函式EnterCriticalSection和LeaveCriticalSection中間的代碼執行過程不會被其他執行緒乾攏或者這么講不允許其他執行緒中
的代碼執行。這樣可以有效防止一個全局變數在兩個執行緒中同時被操作的可能性