📄 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 + -