?? 16.3 線程死鎖16.txt
字號:
16.3 線程死鎖
有一個哲學家進餐的問題能夠很好地描述線程死鎖。哲學家進餐的問題是這樣的:有t 多位哲學家一
起用餐,但每人只有一支筷子,當然用一支筷子是無法吃食物的。這時,如果有一位哲學家能將他
的那只筷子交出來,讓其他哲學家先吃,之后,再將一雙筷子交回來,這樣,所有哲學家就都能夠
吃到食物了。但是哲學家們考慮問題時想得比較深,他們擔心把筷子交給別人先吃,那么別人吃完
食物之后,不把筷子交回來的話,自己不就吃不到食物了嗎?所以他們都希望其他人先把筷子交出來,
讓自己先吃。然而由于每位哲學家都這樣想,從而導致每位哲學家都不肯交出筷子,于是所有的哲
學家看著滿桌的美食,可就是吃不到。這就是一個線程死鎖的實例。
對多線程為說,如果線程1擁有了臨界區對象A,等待臨界區對象B的擁有權,線程 2擁有了臨界區對
象B,等待臨界區對象A的擁有權,這就造成了死鎖。下面通過代碼來演示線程死鎖的發生。我們在
已有的Critical程序上進行修改,結果如例 16-5所示。
例J 16-5
#include <Windows . h>
#inc lude <iostream.h>
DWORD WINAPI Fun1Proc( LPVOID lpParameter 11 thread data
DWORD WINAPI Fun2Proc(
LPVOID lpParameter 11 thread data
int tickets=100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
void main()
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL , 0, Fun1Proc , NULL , 0, NULL);
hThread2=CreateThread(NULL, 0 , Fun2Proc , NULL , 0 , NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&g_csA);
InitializeCriticalSection(&g_csB); ;
Sleep(4000);
DeleteCriticalSection(&g_csA);
DeleteCriticalSection(&g_csB);
DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data
while (TRUE)
EnterCriticalSection(&g_csA);
Sleep(1);
EnterCriticalSection(&g_csB);
if(tickets>0)
Sleep(1);
cout<<"threadl sell ticket : "<<tickets--<<endl;
LeaveCriticalSection(&g_csB);
LeaveCriticalSection(&g_csA);
}
else
{
LeaveCriticalSection(&csa);
LeaveCriticalSection(&csb);
break;
}
}
cout<<"fun1proc is running"<<endl;
return 0;
DWORD WINAPI Fun2Proc( LPVOID lpPararneter // thread data
while (TRUE)
EnterCriticalSection(&g_csB);
Sleep (1) ;
EnterCriticalSection(&g_csA);
if(tickets>0)
{
Sleep (1) ;
cout<<"thread2 sell ticket : "<<tickets--<<endl;
LeaveCriticalSection(&g_csA) ;
LeaveCriticalSection(&g_csB) ;
else
LeaveCriticalSection(&g一cSA) ;
LeaveCriticalSection(&g_csB);
break; '
cout<<"thread2 is running!"<<endl;
return 0;
首先,上述例 16-5所示代碼創建了兩個臨界區對象 :G_csA和 g_csB。在程序中,如果需要對某一種
資源進行保護的話,就可以構建一個臨界區對象。這與現實生活中的情況是一樣的,例如如果在程
序中想要保護電話這種資源的話,那就構建一個臨界區對象來實現,就好像建立一個電話亭一樣:
如果在程序中還想訪問自動柜員機這種資源的話,就可以再創建一個臨界區對象,對自動柜員機這
種資源進行保護。
接著,上述例 16-5所示代碼在 main函數中,調用 InitializeCriticalSection函數對新創建的兩
個臨界區對象 G_csA和 G_csB都進行了初始化,并在程序退出前調用 DeleteCritical Section函數釋
放這兩個臨界區對象(已經沒有任何線程擁有它們了)的所有資源。
然后,在線程 1中調用 EnterCriticalSection函數先請求臨界區對象: g_csA的所有權,當得到該
所有權后,再去請求臨界區對象 =ιcsB的所有權。當線程 1訪問完保護的資源后,調用
LeaveCriticalSection函數釋放兩個臨界區對象的所有權。注意 z因為調用 LeaveCriticalSection
函數時,該函數會立即返回,并不會導致線程等待,所以釋放臨界區對象所有權的順序是無所謂的。
對線程 2來說,官先請求臨界區對象 g_csB的所有權,然后再去等待臨界區對象 ιcsA的所有權。在
訪問完保護的資源之后,釋放所有臨界區對象的所有權。
下面,我們來分析上述程序的執行過程。當線程 1得到臨界區對象 g_csA的所有權之后,調用 Sleep
函數,該線程將睡眠 lms,放棄執行機會。于是,操作系統會選擇線程 2執行,該線程首先等待的
是臨界區對象 g_csB的所有權,當它得到該所有權之后,調用 Sleep函數,讓線程 2也睡眠 lms。
于是,輪到線程 1執行,這時它需要等待臨界區對象 ιcsB的所有權。然而這時臨界區對象 g_csB己
經被線程 2所擁有,因此線程 l就會等待。當線程 1等待時,線程 2開始執行,這時它需要等待臨
界區對象 ιcsA的所有權。然而臨界區對象 ιcsA的所有權己經被線程 1所擁有,因此線程 2也進入
等待狀態。這樣就導致線程 1和線程 2都在等待對方交出臨界區對象的所有權,于是就造成了死鎖。
我們可以運行這時的 CriticaJ程序,將會看到如圖 16.4所示的結果。可以看到,線程 l和線程 2
都沒有執行關鍵代碼段中的代碼,說明它們都沒有得到所需的臨界區對象的所有權。
圖 16 .4線程死鎖結果
因此在利用多線程技術編寫程序的過程中,在實現線程同步時一定要多加注意,應避免發生線程死
鎖。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -