簡介
不論是硬體臨界資源,還是軟體臨界資源,多個執行緒必須互斥地對它進行訪問。每個執行緒中訪問臨界資源的那段代碼稱為臨界區(Critical Section)。
每個執行緒中訪問臨界資源的那段程式稱為臨界區(Critical Section)(臨界資源是一次僅允許一個執行緒使用的共享資源)。每次只準許一個執行緒進入臨界區,進入後不允許其他執行緒進入。不論是硬體臨界資源,還是軟體臨界資源,多個執行緒必須互斥地對它進行訪問。
多個執行緒中涉及到同一個臨界資源的臨界區稱為相關臨界區。
執行緒進入臨界區的調度原則是: ①如果有若干執行緒要求進入空閒的臨界區,一次僅允許一個執行緒進入。②任何時候,處於臨界區內的執行緒不可多於一個。如已有執行緒進入自己的臨界區,則其它所有試圖進入臨界區的執行緒必須等待。③進入臨界區的執行緒要在有限時間內退出,以便其它執行緒能及時進入自己的臨界區。④如果執行緒不能進入自己的臨界區,則應讓出CPU,避免執行緒出現“忙等”現象。
如果有多個執行緒試圖同時訪問臨界區,那么在有一個執行緒進入後其他所有試圖訪問此臨界區的執行緒將被掛起,並一直持續到進入臨界區的執行緒離開。臨界區在被釋放後,其他執行緒可以繼續搶占,並以此達到用原子方式操作共享資源的目的。
臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,並分別用EnterCriticalSection()和LeaveCriticalSection()函式去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須經過InitializeCriticalSection()的初始化後才能使用,而且必須確保所有執行緒中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。否則臨界區將不會起到應有的作用,共享資源依然有被破壞的可能。
簡單來說,EnterCriticalSection沒有給資源加鎖,只是給執行緒加了鎖,
對於加了同一種鎖的執行緒,只能依次執行,不許同步執行,
但是對於不加鎖或者加了不同鎖的執行緒,可以同步執行
什麼時候可以用到:
執行緒不多時,全部為他們加同一種鎖,使他們依次執行
或者加兩種鎖,兩個方式同時執行
實例
下面通過一段代碼展示了臨界區在保護多執行緒訪問的共享資源中的作用。通過兩個執行緒來分別對全局變數g_cArray[10]進行寫入操作,用臨界區結構對象g_cs來保持執行緒的同步,並在開啟執行緒前對其進行初始化。為了使實驗效果更加明顯,體現出臨界區的作用,線上程函式對共享資源g_cArray[10]的寫入時,以Sleep()函式延遲1毫秒,使其他執行緒同其搶占CPU的可能性增大。如果不使用臨界區對其進行保護,則共享資源數據將被破壞(參見圖1(a)所示計算結果),而使用臨界區對執行緒保持同步後則可以得到正確的結果(參見圖1(b)所示計算結果)。代碼實現清單附下:
// 臨界區結構對象
CRITICAL_SECTION g_cs;
// 共享資源
char g_cArray[10];
UINT ThreadProc10(LPVOID pParam)
{
// 進入臨界區
EnterCriticalSection(&g_cs);
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray = a;
Sleep(1);
}
// 離開臨界區
LeaveCriticalSection(&g_cs);
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
// 進入臨界區
EnterCriticalSection(&g_cs);
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = b;
Sleep(1);
}
// 離開臨界區
LeaveCriticalSection(&g_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
// 初始化臨界區
InitializeCriticalSection(&g_cs);
// 啟動執行緒
AfxBeginThread(ThreadProc10, NULL);
AfxBeginThread(ThreadProc11, NULL);
// 等待計算完畢
Sleep(300);
// 報告計算結果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
下面看代碼(全部加同一種鎖):
UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
EnterCriticalSection(&pthis->g_cs);
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "threadOne: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs);
return 0;
}
UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
EnterCriticalSection(&pthis->g_cs);
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "\nthreadSecond: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs);
return 0;
}
此時開啟兩個執行緒的話
InitializeCriticalSection(&g_cs);
str = "";
value = 0;
hThread1 = AfxBeginThread((AFX_THREADPROC)fThread1, this);
hThread2 = AfxBeginThread((AFX_THREADPROC)fThread2, this);
執行結果為:(優先執行先加鎖的)
threadOne: 1
threadOne: 2
threadOne: 3
threadOne: 4
threadOne: 5
threadOne: 6
threadOne: 7
threadOne: 8
threadOne: 9
threadOne: 10
threadSecond: 11
threadSecond: 12
threadSecond: 13
threadSecond: 14
threadSecond: 15
threadSecond: 16
threadSecond: 17
threadSecond: 18
threadSecond: 19
threadSecond: 20
而第二段代碼(一個加鎖,一個不加鎖):
UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
EnterCriticalSection(&pthis->g_cs);
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "threadOne: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs);
return 0;
}
UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "\nthreadSecond: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
return 0;
}
執行結果為(都可以訪問資源,同步執行):
threadOne: 1
threadSecond: 2
threadOne: 3
threadSecond: 4
threadOne: 5
threadSecond: 6
threadOne: 7
threadSecond: 8
threadOne: 9
threadSecond: 10
threadOne: 11
threadSecond: 12
threadOne: 13
threadSecond: 14
threadOne: 15
threadSecond: 16
threadOne: 17
threadSecond: 18
threadOne: 19
threadSecond: 20
第三段代碼(加了不同的鎖):
UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
EnterCriticalSection(&pthis->g_cs);
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "threadOne: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs);
return 0;
}
UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter)
{
CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;
int i, j;
EnterCriticalSection(&pthis->g_cs2);
for (i = 1; i<=10; i++)
{
pthis->value++;
pthis->str.Format(pthis->str + "\nthreadSecond: %d\r\n", pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs2);
return 0;
}
執行結果(同步執行):
threadOne: 1
threadSecond: 2
threadOne: 3
threadSecond: 4
threadOne: 5
threadSecond: 6
threadOne: 7
threadSecond: 8
threadOne: 9
threadSecond: 10
threadOne: 11
threadSecond: 12
threadOne: 13
threadSecond: 14
threadOne: 15
threadSecond: 16
threadOne: 17
threadSecond: 18
threadOne: 19
threadSecond: 20
在使用臨界區時,一般不允許其運行時間過長,只要進入臨界區的執行緒還沒有離開,其他所有試圖進入此臨界區的執行緒都會被掛起而進入到等待狀態,並會在一定程度上影響程式的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他執行緒的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區後無論發生什麼,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的執行緒,而不可用來同步多個進程中的執行緒。
CRITICAL_SECTION 所使用的頭檔案<windows.h>