论文部分内容阅读
摘 要:计算机操作系统课程由于概念抽象,较难理解和掌握,因而课程实验对于加强概念的理解显得尤为重要。死锁是该课程的一个非常核心的概念,本文设计了一个基于windows环境下线程机制的死锁模拟教学实验,程序简单,易于实现和理解。实践表明,该实验在教学过程中收到了良好效果。
关键词:操作系统死锁; 教学实验线程
中图分类号: G642文献标识码:A 文章编号:1674-098X(2012)03(c)-0000-00
计算机操作系统课程是计算机本科专业的一门专业核心课,也是一门考研课程。该课程由于涉及到操作系统的深层次概念和原理,因而对于大多数学生而言,较难理解和掌握。正由于该课程的重要性,很多高校研究人员对该课程的教学方法进行了深入探讨,如文献[1],[2]。而目前的操作系统实验教材虽然不少[3][4],但很少有设计实验直接对死锁现象进行模拟的。本文基于线程机制利用windows环境下API函数实现了一个死锁现象的构建和模拟实验,算法简单,易于被学生理解,教学中可操作性强。
1 死锁的基本概念
设系统中存在一组进程(2个或2个以上进程),其中每一个进程都占用了某些资源,而又都在等待其中另一个进程所占用的资源,如果系统无外力作用,这种等待永远不会结束,则称系统出现了“死锁”,或说这组进程处于“死锁”状态。
死锁是因并发进程竞争资源而引起的一种现象,并发进程对多个资源的共享与竞争有可能导致死锁。死锁可以在2个或多个进程之间发生,也可以在系统全部进程之间产生。死锁是一种与时间有关的错误,在并发系统中,死锁可以在进程通信过程中以及在用信号量作同步工具时,由于P、V操作顺序不当而产生。
因此,产生死锁的原因是,一方面是由于多进程共享的资源不足而引起竞争资源;另一方面是由于进程在运行过程中推进顺序不合法。
2 实验内容设计
问题描述:设有一个仓库存有50箱货物,需要请若干搬运工人搬走。本实验通过两个线程模拟完成2个搬运工人合作搬运货物的过程。
一方面,利用关键代码段(临界区)实现线程同步;另一方面,模拟实现线程死锁。利用线程死锁来让学生体会进程死锁的本质概念。实验采用windows下的Visual C++平台的Console Application进行。
通过本次实验,可以帮助学生熟悉VC下关键代码段(即临界区),和相关API函数。分析线程同步的参考代码,分析运行结果。理解线程同步的概念。
实验中,首先给学生提供一段初始程序,该程序可以实现2个搬运工线程正常合作搬运货物的过程。这样,既可以减少学生实验的难度,提高实验效率,又可以让学生将实验的注意力集中到对同步和死锁问题的理解上去。
然后,要求学生进一步调试和修改代码,并思考以下两个问题:
① 如何修改,使得搬运线程1会始终搬运货物,而搬运线程2始终得不到搬运货物的机会?
② 如果对本次实验给出的参考代码进行修改,制造线程死锁现象?
2.1初始程序及分析
初始程序如下:
#include
#include
DWORD WINAPI FuncProc1(LPVOID
lpParameter);//thread data
DWORD WINAPI FuncProc2(LPVOID
lpParameter);//thread data
int products=50;//货物总数
CRITICAL_SECTION csA;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,FuncProc1,NULL,0,NULL);//创建进程
hThread2=CreateThread(NULL,0,FuncProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&csA);//创建临界区对象
Sleep(4000);
DeleteCriticalSection(&csA);//程序退出前释放临界区对象资源
}
DWORD WINAPI FuncProc1(LPVOID lpParameter)//thread data
{
while(TRUE)
{
EnterCriticalSection(&csA);//判断能否进临界区
Sleep(1);
if(products>0)
{
Sleep(1);
cout<<”thread1 move product:”< LeaveCriticalSection(&csA);//释放临界区对象
}
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;
}
程序运行结果及简要分析如下,
经过编译运行后,输出如下结果:
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
请按任意键继续. .
临界区被占用的时间一般不允许过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。即,必须确保EnterCriticalSection()语句和与之匹配的LeaveCriticalSection()语句都能够被完整执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
2.2 构建死锁现象
怎样修改这个程序,才有可能使其产生死锁呢?关键在于临界区的个数要多于一个。换句话说,只要临界区数大于1,并且线程数多于1,就有可能出现死锁。
其源程序代码修改如下:
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,FuncProc1,NULL,0,NULL);//创建线程
hThread2=CreateThread(NULL,0,FuncProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&csA);//创建临界区对象
InitializeCriticalSection(&csB);
Sleep(4000);
DeleteCriticalSection(&csA);//程序退出前释放临界区对象资源
}
DWORD WINAPI FuncProc1(
LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&csA);//判断能否进入临界区
Sleep(1);
if(products>0)
{EnterCriticalSection(&csB);
Sleep(1);
cout<<"threadl move product:"< LeaveCriticalSection(&csA);//释放临界区对象所有权
}
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;
}
由此我们再引导学生回顾产生死锁的必要条件:
① 互斥使用资源条件;
② 占有和等待条件;
③ 不剥夺条件;
④ 循环等待条件。
可以发现,两个搬运线程之所以产生死锁,正是满足了以上死锁的必要条件。
3 讨论
由线程概念扩展到进程概念。当系统中有2个甚至多个临界区,并且多个进程同时共享这些临界区时,进程之间因为资源竞争关系很可能会发生死锁,使死锁发生的几率增大。此时,我们可以通过以下思路来预防死锁,即通过给每个进程所需要的资源(临界区)进行编号,并且让进程在申请这些资源时,采取按序申请的原则(序号从小到大或从大到小)逐一申请,这样,可以在一定程度可以预防死锁,使系统中各程序能正常运行下去。
这一思想正是操作系统教材中提出的有序资源分配法,由此,我们将死锁的产生、过程和预防办法都进行了简要分析。
4 结语
本文设计了一个基于线程机制的操作系统课程的死锁模拟教学实验,通过本实验的学习,可以帮助学生达到以下实验效果:
(1)理解线程的概念;
(2)熟悉进程、线程同步的概念;
(3)深刻理解死锁的本质;
(4)深入理解操作系统对多线程调度的管理;
(5)深刻理解临界区和互斥的概念。
并由此举一反三,延伸到进程死锁的概念。经教学实验的实践,学生通过本实验加深了对死锁的概念的理解和掌握,收效良好。
参考文献
[1]马晓慧。操作系统课程教学方法探索[J],计算机教育,2011
[2]刘晓平,陈欣,李琳,路强,田卫东。面向操作系统课程的“启发—探究式”教学方法初探[J]。计算机教育,2011
[3]王煜,张明,刘振鹏。操作系统习题解答与实验指导,中国铁道出版社,2004
关键词:操作系统死锁; 教学实验线程
中图分类号: G642文献标识码:A 文章编号:1674-098X(2012)03(c)-0000-00
计算机操作系统课程是计算机本科专业的一门专业核心课,也是一门考研课程。该课程由于涉及到操作系统的深层次概念和原理,因而对于大多数学生而言,较难理解和掌握。正由于该课程的重要性,很多高校研究人员对该课程的教学方法进行了深入探讨,如文献[1],[2]。而目前的操作系统实验教材虽然不少[3][4],但很少有设计实验直接对死锁现象进行模拟的。本文基于线程机制利用windows环境下API函数实现了一个死锁现象的构建和模拟实验,算法简单,易于被学生理解,教学中可操作性强。
1 死锁的基本概念
设系统中存在一组进程(2个或2个以上进程),其中每一个进程都占用了某些资源,而又都在等待其中另一个进程所占用的资源,如果系统无外力作用,这种等待永远不会结束,则称系统出现了“死锁”,或说这组进程处于“死锁”状态。
死锁是因并发进程竞争资源而引起的一种现象,并发进程对多个资源的共享与竞争有可能导致死锁。死锁可以在2个或多个进程之间发生,也可以在系统全部进程之间产生。死锁是一种与时间有关的错误,在并发系统中,死锁可以在进程通信过程中以及在用信号量作同步工具时,由于P、V操作顺序不当而产生。
因此,产生死锁的原因是,一方面是由于多进程共享的资源不足而引起竞争资源;另一方面是由于进程在运行过程中推进顺序不合法。
2 实验内容设计
问题描述:设有一个仓库存有50箱货物,需要请若干搬运工人搬走。本实验通过两个线程模拟完成2个搬运工人合作搬运货物的过程。
一方面,利用关键代码段(临界区)实现线程同步;另一方面,模拟实现线程死锁。利用线程死锁来让学生体会进程死锁的本质概念。实验采用windows下的Visual C++平台的Console Application进行。
通过本次实验,可以帮助学生熟悉VC下关键代码段(即临界区),和相关API函数。分析线程同步的参考代码,分析运行结果。理解线程同步的概念。
实验中,首先给学生提供一段初始程序,该程序可以实现2个搬运工线程正常合作搬运货物的过程。这样,既可以减少学生实验的难度,提高实验效率,又可以让学生将实验的注意力集中到对同步和死锁问题的理解上去。
然后,要求学生进一步调试和修改代码,并思考以下两个问题:
① 如何修改,使得搬运线程1会始终搬运货物,而搬运线程2始终得不到搬运货物的机会?
② 如果对本次实验给出的参考代码进行修改,制造线程死锁现象?
2.1初始程序及分析
初始程序如下:
#include
#include
DWORD WINAPI FuncProc1(LPVOID
lpParameter);//thread data
DWORD WINAPI FuncProc2(LPVOID
lpParameter);//thread data
int products=50;//货物总数
CRITICAL_SECTION csA;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,FuncProc1,NULL,0,NULL);//创建进程
hThread2=CreateThread(NULL,0,FuncProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&csA);//创建临界区对象
Sleep(4000);
DeleteCriticalSection(&csA);//程序退出前释放临界区对象资源
}
DWORD WINAPI FuncProc1(LPVOID lpParameter)//thread data
{
while(TRUE)
{
EnterCriticalSection(&csA);//判断能否进临界区
Sleep(1);
if(products>0)
{
Sleep(1);
cout<<”thread1 move product:”<
}
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:"<
}
else
{
LeaveCriticalSection(&csA);
break;
}
}
return 0;
}
程序运行结果及简要分析如下,
经过编译运行后,输出如下结果:
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
请按任意键继续. .
临界区被占用的时间一般不允许过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。即,必须确保EnterCriticalSection()语句和与之匹配的LeaveCriticalSection()语句都能够被完整执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
2.2 构建死锁现象
怎样修改这个程序,才有可能使其产生死锁呢?关键在于临界区的个数要多于一个。换句话说,只要临界区数大于1,并且线程数多于1,就有可能出现死锁。
其源程序代码修改如下:
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,FuncProc1,NULL,0,NULL);//创建线程
hThread2=CreateThread(NULL,0,FuncProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&csA);//创建临界区对象
InitializeCriticalSection(&csB);
Sleep(4000);
DeleteCriticalSection(&csA);//程序退出前释放临界区对象资源
}
DWORD WINAPI FuncProc1(
LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&csA);//判断能否进入临界区
Sleep(1);
if(products>0)
{EnterCriticalSection(&csB);
Sleep(1);
cout<<"threadl move product:"<
}
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:"<
}
else
{LeaveCriticalSection(&csA);
break;
}
}
return 0;
}
由此我们再引导学生回顾产生死锁的必要条件:
① 互斥使用资源条件;
② 占有和等待条件;
③ 不剥夺条件;
④ 循环等待条件。
可以发现,两个搬运线程之所以产生死锁,正是满足了以上死锁的必要条件。
3 讨论
由线程概念扩展到进程概念。当系统中有2个甚至多个临界区,并且多个进程同时共享这些临界区时,进程之间因为资源竞争关系很可能会发生死锁,使死锁发生的几率增大。此时,我们可以通过以下思路来预防死锁,即通过给每个进程所需要的资源(临界区)进行编号,并且让进程在申请这些资源时,采取按序申请的原则(序号从小到大或从大到小)逐一申请,这样,可以在一定程度可以预防死锁,使系统中各程序能正常运行下去。
这一思想正是操作系统教材中提出的有序资源分配法,由此,我们将死锁的产生、过程和预防办法都进行了简要分析。
4 结语
本文设计了一个基于线程机制的操作系统课程的死锁模拟教学实验,通过本实验的学习,可以帮助学生达到以下实验效果:
(1)理解线程的概念;
(2)熟悉进程、线程同步的概念;
(3)深刻理解死锁的本质;
(4)深入理解操作系统对多线程调度的管理;
(5)深刻理解临界区和互斥的概念。
并由此举一反三,延伸到进程死锁的概念。经教学实验的实践,学生通过本实验加深了对死锁的概念的理解和掌握,收效良好。
参考文献
[1]马晓慧。操作系统课程教学方法探索[J],计算机教育,2011
[2]刘晓平,陈欣,李琳,路强,田卫东。面向操作系统课程的“启发—探究式”教学方法初探[J]。计算机教育,2011
[3]王煜,张明,刘振鹏。操作系统习题解答与实验指导,中国铁道出版社,2004