一个简单有效的即时检测线程死锁的方法(附c++源代码)(原创)
2018-05-17 17:52
190 查看
文章原创,如若转载,请标明出处。
通常来说,死锁就是线程之间发生锁资源的抢夺,比方说:线程1拥有了锁A未释放而还想去拥锁B,而线程2拥有了锁B未释放却还想去拥有锁A,于是乎他们互相等待,谁都获取不到新锁资源。如下图: 已经拥有的锁 还想拥有的锁线程1 A B线程2 B A当然上述情况是最简单的死锁,通常死锁还可能在多个线程发生,他们之间相互抢夺,各不相让。如下图: 已经拥有的锁 还想拥有的锁线程1 A C线程2 B A线程3 C B以上是三个线程的锁资源抢夺,当更多线程加入锁资源抢夺的时候,这种情况会更加的复杂。首先这些锁占用和锁需求我们可以记录起来,即每个锁被哪个线程锁锁占用,我们把这个线程的TID记录下来:
void lock(void) const { ::EnterCriticalSection(&m_sesion); m_dwOwnedThreadId = GetCurrentThreadId(); //当前线程拥有了锁资源 } void unlock(void) const { ::LeaveCriticalSection(&m_sesion); m_dwOwnedThreadId = 0; //当前线程释放了锁资源 } mutable DWORD m_dwOwnedThreadId; //已经拥有锁的线程, 一个锁对象只能被一个线程占用但我们仍然需要一个全局变量来记录当前线程以及当前线程需要获取已经被占用锁的线程之间的关系(只保留停留在等待锁的线程关系,已经获取了锁的线程先不管),就像入上图的层次关系:
std::map<DWORD, DWORD> g_mapCurTidWantOwnResTid; //定义全局变量,记录当前等待锁资源的线程ID,以及对应这个锁已经被其他线程占用的线程ID。这样就生成了如上图的对应关系,代码如下: void lock(void) const { if(m_dwOwnedThreadId != 0 && m_dwOwnedThreadId != GetCurrentThreadId()) //锁已经被占用,需要等待而无法进入了 如果锁被当前线程占用,那么依旧可以重入,也就是一个线程可以多次调用lock()函数而不会阻塞 { cout<<"当前线程:"<<GetCurrentThreadId()<<" 想获取已经被线程:"<<m_dwOwnedThreadId<<" 占用的锁:"<<endl; g_mapCurTidWantOwnResTid[GetCurrentThreadId()] = m_dwOwnedThreadId; //记录对应的关系 } ::EnterCriticalSection(&m_sesion); m_dwOwnedThreadId = GetCurrentThreadId();//当前线程拥有了锁资源 for (std::map<DWORD, DWORD>::iterator it = g_mapCurTidWantOwnResTid.begin(); it != g_mapCurTidWantOwnResTid.end(); it++) { if(it->first == m_dwOwnedThreadId) { g_mapCurTidWantOwnResTid.erase(it); //已经获取了锁 解除对应关系 break; } } } void unlock(void) const { ::LeaveCriticalSection(&m_sesion); m_dwOwnedThreadId = 0; //当前线程释放了锁资源 }
三个线程运行时,发生死锁状态的三个线程代码如下:
DWORD WINAPI DeadLockThread1( PVOID pVoid ) { mutexA.lock(); cout<<" mutexA.locked "<<endl; Sleep(3500); mutexC.lock(); //线程1,拥有A,还想C } DWORD WINAPI DeadLockThread2( PVOID pVoid ) { mutexB.lock(); cout<<" mutexB.locked "<<endl; Sleep(3000); mutexA.lock(); //线程2,拥有B,还想A } DWORD WINAPI DeadLockThread3( PVOID pVoid ) { mutexC.lock(); cout<<" mutexC.locked "<<endl; Sleep(2000); mutexB.lock(); //线程3,拥有C,还想B }
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A31%3A7.png?version=1&modificationDate=1526463083000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A24%3A42.png?version=1&modificationDate=1526462698000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A33%3A2.png?version=1&modificationDate=1526463198000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A27%3A31.png?version=1&modificationDate=1526462867000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A15%3A17.png?version=1&modificationDate=1526462133000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-17%2014%3A25%3A49.png?version=1&modificationDate=1526538366697&api=v2)
void lock(void) const { if(m_dwOwnedThreadId != 0 && m_dwOwnedThreadId != GetCurrentThreadId()) //锁已经被占用,需要等待而无法进入了 如果锁被当前线程占用,那么依旧可以重入,也就是一个线程可以多次调用lock()函数而不会阻塞 { cout<<"当前线程:"<<GetCurrentThreadId()<<" 想获取已经被线程:"<<m_dwOwnedThreadId<<" 占用的锁"<<endl; g_mapCurTidWantOwnResTid[GetCurrentThreadId()] = m_dwOwnedThreadId; std::map<DWORD, DWORD> mapCurTidWantOwnResTidTmp = g_mapCurTidWantOwnResTid; if(RecurFindIsDeadlocked(GetCurrentThreadId(), GetCurrentThreadId(), m_dwOwnedThreadId, mapCurTidWantOwnResTidTmp)) //递归查找是否形成了锁资源抢夺的闭环 { cout<<"当前线程:"<<GetCurrentThreadId()<< " 发生了死锁!"<<endl; } } ::EnterCriticalSection(&m_sesion); m_dwOwnedThreadId = GetCurrentThreadId(); for (std::map<DWORD, DWORD>::iterator it = g_mapCurTidWantOwnResTid.begin(); it != g_mapCurTidWantOwnResTid.end(); it++) { if(it->first == m_dwOwnedThreadId) { g_mapCurTidWantOwnResTid.erase(it); //已经获取了锁 解除关系 break; } } } bool RecurFindIsDeadlocked(DWORD dwFirstKey, DWORD dwKey, DWORD dwVal, std::map<DWORD, DWORD>& mapCurTidWantOwnResTid) //递归查找 { bool bRet = false; for (std::map<DWORD, DWORD>::iterator it = mapCurTidWantOwnResTid.begin(); it != mapCurTidWantOwnResTid.end(); it++) //把自己当前关系移除 防止进入递归死循环 { if(dwKey == it->first && dwVal == it->second) { mapCurTidWantOwnResTid.erase(it); break; } } for (std::map<DWORD, DWORD>::iterator it = mapCurTidWantOwnResTid.begin(); it != mapCurTidWantOwnResTid.end(); it++) { if(it->first == dwVal) //当前键值关系是当前的 key== 前一层关系的value { if(dwFirstKey == it->second) //当前键值关系的value 就是最开始自己想找的 A出发最后总算找到了A { return true; } return RecurFindIsDeadlocked(dwFirstKey, it->first, it->second, mapCurTidWantOwnResTid); //继续下一层的查找 } } return bRet; }
好,验证下:两个线程发生死锁状态:
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A46%3A20.png?version=1&modificationDate=1526463996000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-16%2017%3A47%3A16.png?version=1&modificationDate=1526464052000&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-17%2017%3A29%3A47.png?version=1&modificationDate=1526549405220&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-17%2017%3A36%3A26.png?version=1&modificationDate=1526549803913&api=v2)
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-17%2017%3A39%3A33.png?version=1&modificationDate=1526549991326&api=v2)
话题的拓展:其实死锁不止发生在像上面的线程间锁的竞争上,还包括线程间的相互等待上,看下面的例子:
DWORD WINAPI WaitDeadLockThread1(LPVOID lpParameter) { cout<<"WaitDeadLockThread1, and want lock mutexA"<<endl; mutexA.lock(); //想获取已经被主线程占用的锁 return 0; } int _tmain(int argc, _TCHAR* argv[]) { mutexA.lock(); cout<<"main thread mutexA.locked "<<endl; DWORD dwTid1; HANDLE hThread1 = CreateThread(NULL, 0, WaitDeadLockThread1, 0, 0, &dwTid1); cout<<"当前线程:"<<GetCurrentThreadId()<<" 正在等待线程:"<<dwTid1<<" 执行完成!"<<endl; WaitForSingleObject(hThread1, INFINITE); //等待线程WaitDeadLockThread1的执行完成,但是上面这个线程其实已经死锁了 cout<<"main thread wait finish"<<endl; }
像上面的情况,发生死锁的原因并不是因为在等待一个闭环的锁而导致的了,而是因为其中一个线程等待另一个想得到锁的线程而导致的。执行后输出的结果如下:
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-18%2014%3A51%3A36.png?version=1&modificationDate=1526626316186&api=v2)
int _tmain(int argc, _TCHAR* argv[]) { mutexA.lock(); cout<<"main thread mutexA.locked "<<endl; DWORD dwTid1; HANDLE hThread1 = CreateThread(NULL, 0, WaitDeadLockThread1, 0, 0, &dwTid1); g_mapCurTidWantOwnResTid[GetCurrentThreadId()] = dwTid1; //把自己正在等待的线程ID和等待的目标线程ID都记录起来 cout<<"当前线程:"<<GetCurrentThreadId()<<" 正在等待线程:"<<dwTid1<<" 执行完成!"<<endl; WaitForSingleObject(hThread1, INFINITE); for (std::map<DWORD, DWORD>::iterator it = g_mapCurTidWantOwnResTid.begin(); it != g_mapCurTidWantOwnResTid.end(); it++) //等待完成之后记得移除这个等待关系 { if(it->first == GetCurrentThreadId()) { g_mapCurTidWantOwnResTid.erase(it); //已完成了等待 解除关系 break; } } cout<<"main thread wait finish"<<endl; }好,我们再试试,运行程序:
![](http://45.124.252.208:8090/download/attachments/4178051/image2018-5-18%2014%3A50%3A12.png?version=1&modificationDate=1526626231618&api=v2)
代码下载地址:https://download.csdn.net/download/liaozhilong88/10429911 阅读更多
相关文章推荐
- C++中提供了多种基本的数据类型。实际上,这些远不能满足我们的需求,如复数(第10章的例子大多是处理虚数的),再如分数。本任务将设计一个简单的分数类,完成对分数的几个运算。一则巩固基于对象编程的方法,
- 一个 Linux 上分析死锁的简单方法
- 一个 Linux 上分析死锁的简单方法
- 一个 Linux 上分析死锁的简单方法
- .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法
- 使用C++与SFML编写一个简单的撞球游戏Part7——弹球的碰撞检测
- 一个最简单的OSG例子源代码(C++)
- [原创]一个简单的使用C++在运行时获取调用堆栈的类
- C++实现一个简单的双线程MVC框架
- 一个 Linux 上分析死锁的简单方法
- (原创)实现一个简单的字符串格式化方法
- [原创]一个为Process取得SYSTEM令牌的简单方法
- C++里面亲测有效的random write 一个文件的方法
- .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法
- C++第5周任务【任务2】本任务将设计一个简单的分数类,完成对分数的几个运算。一则巩固基于对象编程的方法,二则也为第10章做运算符重载等积累些感性认识。
- c++一个简单的线程
- 一个简单的C#多线程间同步(事件方法)的例子
- [c/c++] 一个进程有多个线程,用什么方法让主线程不退出更好,占用资源最少,效率最高?
- 有感于线程中断的一个方法(原创)
- 一个 Linux 上分析死锁的简单方法