您的位置:首页 > 编程语言 > C语言/C++

一个简单有效的即时检测线程死锁的方法(附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
}

   输出情况:  当两个线程运行时,发生死锁状态输出如下:  接下来关键要看上图中当前已经拥有锁和想拥有已经被占用的锁的相互关系了: 1、线程1拥有锁A,想拥有锁C2、锁C被线程3占用,但线程3想拥有锁B3、锁B被线程2占用,但线程2想拥有锁A4、锁A被线程1占用根据上面的相互关系我们可以看出来,如果形成死锁,那么至少会形成一个资源引用的闭环,否则认为没有发生死锁。即从A出发,最后还能回到A;同样两个线程死锁的相互关系如下:  于是乎,我们可以在lock()函数等待使用锁的过程中即可判断当前锁资源访问有没有形成资源抢夺闭环: 
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;
}

 好,验证下:两个线程发生死锁状态: 三个线程生死锁状态: 注意:只有在最后一个线程加入到锁抢夺的时候才形成了闭环,才真正形成了死锁。 好,这个时候如果发现了死锁,我们要怎么判断哪里出了问题呢。1.如果在调试机器出现,可以用IDE直接attach到这个进程,然后下断点,查看堆栈2.使用外部工具。首先打开大名鼎鼎的Process Explorer(procexp.exe)工具: 然后找到对应的进程:DeadLock.exe,然后双击进程名称会跳转到进程属性页: 然后我们在选中一个死锁的线程,双击选中的项,就可以看到这个线程的调用堆栈了:

话题的拓展:其实死锁不止发生在像上面的线程间锁的竞争上,还包括线程间的相互等待上,看下面的例子:
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;
}

像上面的情况,发生死锁的原因并不是因为在等待一个闭环的锁而导致的了,而是因为其中一个线程等待另一个想得到锁的线程而导致的。执行后输出的结果如下: 解决方案:对于这种问题其实也是相类似的处理,只需要在主线程执行WaitForSingleObject之前把当前线程和需要等待的目标线程ID关系都记录起来,当等待结束后移除这个等待关系即可:
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;
}
好,我们再试试,运行程序:
代码下载地址:https://download.csdn.net/download/liaozhilong88/10429911

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐