摘 要:計算機(jī)操作系統(tǒng)課程由于概念抽象,較難理解和掌握,因而課程實驗對于加強(qiáng)概念的理解顯得尤為重要。死鎖是該課程的一個非常核心的概念,本文設(shè)計了一個基于windows環(huán)境下線程機(jī)制的死鎖模擬教學(xué)實驗,程序簡單,易于實現(xiàn)和理解。實踐表明,該實驗在教學(xué)過程中收到了良好效果。
關(guān)鍵詞:操作系統(tǒng)死鎖; 教學(xué)實驗線程
中圖分類號: G642文獻(xiàn)標(biāo)識碼:A 文章編號:1674-098X(2012)03(c)-0000-00
計算機(jī)操作系統(tǒng)課程是計算機(jī)本科專業(yè)的一門專業(yè)核心課,也是一門考研課程。該課程由于涉及到操作系統(tǒng)的深層次概念和原理,因而對于大多數(shù)學(xué)生而言,較難理解和掌握。正由于該課程的重要性,很多高校研究人員對該課程的教學(xué)方法進(jìn)行了深入探討,如文獻(xiàn)[1],[2]。而目前的操作系統(tǒng)實驗教材雖然不少[3][4],但很少有設(shè)計實驗直接對死鎖現(xiàn)象進(jìn)行模擬的。本文基于線程機(jī)制利用windows環(huán)境下API函數(shù)實現(xiàn)了一個死鎖現(xiàn)象的構(gòu)建和模擬實驗,算法簡單,易于被學(xué)生理解,教學(xué)中可操作性強(qiáng)。
1 死鎖的基本概念
設(shè)系統(tǒng)中存在一組進(jìn)程(2個或2個以上進(jìn)程),其中每一個進(jìn)程都占用了某些資源,而又都在等待其中另一個進(jìn)程所占用的資源,如果系統(tǒng)無外力作用,這種等待永遠(yuǎn)不會結(jié)束,則稱系統(tǒng)出現(xiàn)了“死鎖”,或說這組進(jìn)程處于“死鎖”狀態(tài)。
死鎖是因并發(fā)進(jìn)程競爭資源而引起的一種現(xiàn)象,并發(fā)進(jìn)程對多個資源的共享與競爭有可能導(dǎo)致死鎖。死鎖可以在2個或多個進(jìn)程之間發(fā)生,也可以在系統(tǒng)全部進(jìn)程之間產(chǎn)生。死鎖是一種與時間有關(guān)的錯誤,在并發(fā)系統(tǒng)中,死鎖可以在進(jìn)程通信過程中以及在用信號量作同步工具時,由于P、V操作順序不當(dāng)而產(chǎn)生。
因此,產(chǎn)生死鎖的原因是,一方面是由于多進(jìn)程共享的資源不足而引起競爭資源;另一方面是由于進(jìn)程在運(yùn)行過程中推進(jìn)順序不合法。
2 實驗內(nèi)容設(shè)計
問題描述:設(shè)有一個倉庫存有50箱貨物,需要請若干搬運(yùn)工人搬走。本實驗通過兩個線程模擬完成2個搬運(yùn)工人合作搬運(yùn)貨物的過程。
一方面,利用關(guān)鍵代碼段(臨界區(qū))實現(xiàn)線程同步;另一方面,模擬實現(xiàn)線程死鎖。利用線程死鎖來讓學(xué)生體會進(jìn)程死鎖的本質(zhì)概念。實驗采用windows下的Visual C++平臺的Console Application進(jìn)行。
通過本次實驗,可以幫助學(xué)生熟悉VC下關(guān)鍵代碼段(即臨界區(qū)),和相關(guān)API函數(shù)。分析線程同步的參考代碼,分析運(yùn)行結(jié)果。理解線程同步的概念。
實驗中,首先給學(xué)生提供一段初始程序,該程序可以實現(xiàn)2個搬運(yùn)工線程正常合作搬運(yùn)貨物的過程。這樣,既可以減少學(xué)生實驗的難度,提高實驗效率,又可以讓學(xué)生將實驗的注意力集中到對同步和死鎖問題的理解上去。
然后,要求學(xué)生進(jìn)一步調(diào)試和修改代碼,并思考以下兩個問題:
①如何修改,使得搬運(yùn)線程1會始終搬運(yùn)貨物,而搬運(yùn)線程2始終得不到搬運(yùn)貨物的機(jī)會?
②如果對本次實驗給出的參考代碼進(jìn)行修改,制造線程死鎖現(xiàn)象?
2.1初始程序及分析
初始程序如下:
#include
#include
DWORD WINAPI FuncProc1(LPVOID
lpParameter);//thread data
DWORD WINAPI FuncProc2(LPVOID
lpParameter);//thread data
int products=50;//貨物總數(shù)
CRITICAL_SECTION csA;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,F(xiàn)uncProc1,NULL,0,NULL);//創(chuàng)建進(jìn)程
hThread2=CreateThread(NULL,0,F(xiàn)uncProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(csA);//創(chuàng)建臨界區(qū)對象
Sleep(4000);
DeleteCriticalSection(csA);//程序退出前釋放臨界區(qū)對象資源
}
DWORD WINAPI FuncProc1(LPVOID lpParameter)//thread data
{
while(TRUE)
{
EnterCriticalSection(csA);//判斷能否進(jìn)臨界區(qū)
Sleep(1);
if(products>0)
{
Sleep(1);
cout<<”thread1 move product:”< LeaveCriticalSection(csA);//釋放臨界區(qū)對象 } else { LeaveCriticalSection(csA); break; } } return 0; } DWORD WINAPI FuncProc2(LPVOID lpParameter)//thread data { while(TRUE) { EnterCriticalSection(csA); Sleep(1); if(products>0) { Sleep(1); cout<<\"thread2 move product:\"< LeaveCriticalSection(csA); } else { LeaveCriticalSection(csA); break; } } return 0; } 程序運(yùn)行結(jié)果及簡要分析如下, 經(jīng)過編譯運(yùn)行后,輸出如下結(jié)果: thread2 move product:50 threadl move product:49 thread2 move product:48 threadl move product:47 thread2 move product:46 threadl move product:45 …… thread2 move product:2 threadl move product:1 請按任意鍵繼續(xù). . 臨界區(qū)被占用的時間一般不允許過長,只要進(jìn)入臨界區(qū)的線程還沒有離開,其他所有試圖進(jìn)入此臨界區(qū)的線程都會被掛起而進(jìn)入到等待狀態(tài),并會在一定程度上影響程序的運(yùn)行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預(yù)的操作包含到臨界區(qū)。如果進(jìn)入了臨界區(qū)卻一直沒有釋放,同樣也會引起其他線程的長時間等待。即,必須確保EnterCriticalSection()語句和與之匹配的LeaveCriticalSection()語句都能夠被完整執(zhí)行到??梢酝ㄟ^添加結(jié)構(gòu)化異常處理代碼來確保LeaveCriticalSection()語句的執(zhí)行。雖然臨界區(qū)同步速度很快,但卻只能用來同步本進(jìn)程內(nèi)的線程,而不可用來同步多個進(jìn)程中的線程。 2.2 構(gòu)建死鎖現(xiàn)象 怎樣修改這個程序,才有可能使其產(chǎn)生死鎖呢?關(guān)鍵在于臨界區(qū)的個數(shù)要多于一個。換句話說,只要臨界區(qū)數(shù)大于1,并且線程數(shù)多于1,就有可能出現(xiàn)死鎖。 其源程序代碼修改如下: DWORD WINAPI FuncProc1( LPVOID lpParameter); DWORD WINAPI FuncProc2( LPVOID lpParameter); int products=50; CRITICAL_SECTION csA; CRITICAL_SECTION csB; void main() { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,F(xiàn)uncProc1,NULL,0,NULL);//創(chuàng)建線程 hThread2=CreateThread(NULL,0,F(xiàn)uncProc2,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); InitializeCriticalSection(csA);//創(chuàng)建臨界區(qū)對象 InitializeCriticalSection(csB); Sleep(4000); DeleteCriticalSection(csA);//程序退出前釋放臨界區(qū)對象資源 } DWORD WINAPI FuncProc1( LPVOID lpParameter) { while(TRUE) { EnterCriticalSection(csA);//判斷能否進(jìn)入臨界區(qū) Sleep(1); if(products>0) {EnterCriticalSection(csB); Sleep(1); cout<<\"threadl move product:\"< LeaveCriticalSection(csA);//釋放臨界區(qū)對象所有權(quán) } else { LeaveCriticalSection(csA); break; } } return 0; } DWORD WINAPI FuncProc2( LPVOID lpParameter) { while(TRUE) { EnterCriticalSection(csA); Sleep(1); if(products>0) {EnterCriticalSection(csA); Sleep(1); cout<<\"thread2 move product:\"< LeaveCriticalSection(csB); } else {LeaveCriticalSection(csA); break; } } return 0; } 由此我們再引導(dǎo)學(xué)生回顧產(chǎn)生死鎖的必要條件: ①互斥使用資源條件; ②占有和等待條件; ③不剝奪條件; ④循環(huán)等待條件。 可以發(fā)現(xiàn),兩個搬運(yùn)線程之所以產(chǎn)生死鎖,正是滿足了以上死鎖的必要條件。 3 討論 由線程概念擴(kuò)展到進(jìn)程概念。當(dāng)系統(tǒng)中有2個甚至多個臨界區(qū),并且多個進(jìn)程同時共享這些臨界區(qū)時,進(jìn)程之間因為資源競爭關(guān)系很可能會發(fā)生死鎖,使死鎖發(fā)生的幾率增大。此時,我們可以通過以下思路來預(yù)防死鎖,即通過給每個進(jìn)程所需要的資源(臨界區(qū))進(jìn)行編號,并且讓進(jìn)程在申請這些資源時,采取按序申請的原則(序號從小到大或從大到?。┲鹨簧暾垼@樣,可以在一定程度可以預(yù)防死鎖,使系統(tǒng)中各程序能正常運(yùn)行下去。 這一思想正是操作系統(tǒng)教材中提出的有序資源分配法,由此,我們將死鎖的產(chǎn)生、過程和預(yù)防辦法都進(jìn)行了簡要分析。 4 結(jié)語 本文設(shè)計了一個基于線程機(jī)制的操作系統(tǒng)課程的死鎖模擬教學(xué)實驗,通過本實驗的學(xué)習(xí),可以幫助學(xué)生達(dá)到以下實驗效果: (1)理解線程的概念; (2)熟悉進(jìn)程、線程同步的概念; (3)深刻理解死鎖的本質(zhì); (4)深入理解操作系統(tǒng)對多線程調(diào)度的管理; (5)深刻理解臨界區(qū)和互斥的概念。 并由此舉一反三,延伸到進(jìn)程死鎖的概念。經(jīng)教學(xué)實驗的實踐,學(xué)生通過本實驗加深了對死鎖的概念的理解和掌握,收效良好。 參考文獻(xiàn) [1]馬曉慧。操作系統(tǒng)課程教學(xué)方法探索[J],計算機(jī)教育,2011 [2]劉曉平,陳欣,李琳,路強(qiáng),田衛(wèi)東。面向操作系統(tǒng)課程的“啟發(fā)—探究式”教學(xué)方法初探[J]。計算機(jī)教育,2011 [3]王煜,張明,劉振鵬。操作系統(tǒng)習(xí)題解答與實驗指導(dǎo),中國鐵道出版社,2004