如何在C ++ Qt程序中调试多线程死锁?

时间:2014-10-25 18:40:35

标签: c++ multithreading qt

对于多线程死锁错误(或其他多线程相关的错误),这种错误很少发生且难以重复,当它发生时,程序在Windows下冻结,所以我甚至无法连接调试器。 有时甚至只会在某些特定情况下(例如CPU忙时)发生错误。 对于这些错误,是否有任何神奇的软件或技术可以节省我的一天?

编辑: 对不起我的不那么具体的问题,我只是调试一个巨大的软件,没有其他人编写的单行文档。所以我只想知道有没有像代码分析器或任何可以快速检测多线程错误的有用技术?

1 个答案:

答案 0 :(得分:3)

如果您可以在MacOS / X下运行您的程序,当程序处于死锁状态时,一件有用的事情就是启动Activity Monitor,选择您的Process,然后执行“Sample Process”或“Run Spindump” ”。这将显示进程中每个线程的当前堆栈跟踪 - 很可能你会发现在lock()调用中阻塞了两个(或更多)线程,并且从检查那些lock()调用的位置你可以看出哪些互斥量是造成僵局的因素。这对于弄清楚僵局是如何发生的还有很长的路要走。

或者,如果您可以在Linux下构建程序,则可以使用valgrind的helgrind工具来检测潜在的死锁。其他操作系统可能有类似的工具,我不确定。

如果没有,那么您需要分析在程序中获取锁的所有序列。对于一个小程序,您可以通过以下方式完成此操作 - 遍历所有代码路径并记下获取的锁,以及按什么顺序。你特别感兴趣的是一个线程一次持有多个锁的情况 - 如果有另一个线程也持有这些锁,而另一个线程没有以与第一个相同的顺序获取这些锁线程,那是一个潜在的僵局。另一方面,线程获取单个锁然后释放它(同时不获取任何其他锁)的情况永远不会导致死锁,因此您可以忽略它们。

如果您的程序太大/太复杂,无法进行手动分析,并且您没有自动化工具来检查锁定采集序列,那么您可以通过一些工作“自己动手”。您要做的是使用包装函数搜索并替换所有程序的锁定命令,该函数在获取锁定之前打印出一些调试信息。包装函数看起来像这样:

#define my_lock(theMutex) my_lock_aux(__FILE__, __LINE__, theMutex)
void my_lock_aux(const char * file, int line, QMutex & theMutex)
{
   printf("Thread %i is about to lock mutex %p at [%s:%i]\n", (int)pthread_self(), &theMutex, file, line);
   theMutex.lock();
}

...并为您的unlock()调用做类似的事情:

#define my_unlock(theMutex) my_unlock_aux(__FILE__, __LINE__, theMutex)
void my_unlock_aux(const char * file, int line, QMutex & theMutex)
{
  theMutex.unlock();
  printf("Thread %i unlocked mutex %p at [%s:%i]\n", (int)pthread_self(),  &theMutex, file, line);
}

完成并完成编译后,您可以运行程序,它会将大量输出打印到stdout。将其重定向到文件,给你的程序一些练习(注意:你实际上不必重现死锁,你只需要获得程序行为的代表性示例),然后退出程序。

现在你有一个文本文件,其中包含“Thread 1234即将锁定[...]的互斥锁”和“Thread 31415解锁的mutex blah at [...]”消息,你可以编写一个小程序解析该文件以自动确定每个线程同时保持的互斥体集,以及该线程获取这些互斥体的顺序。

一旦该程序完成解析文件,您就可以打印出它找到的所有多锁获取序列,这样您就可以直观地看到不一致排序的位置;例如它可能打印出这样的东西:

 Thread 1234 acquired 4 locks simultaneously:
    -> Lock 0x1236782 was acquired at somefile.cpp:128
    -> Lock 0x2304890 was acquired at anotherfile.cpp:57
    -> Lock 0x0945820 was acquired at yetanotherfile.c:562
    -> Lock 0x2345824 was acquired at somefile.c:125

 Thread 4261 acquired 2 locks simultaneously:
    -> Lock 0x0945820 was acquired at yetanotherfile.c:562
    -> Lock 0x2304890 was acquired at anotherfile.cpp:57

...然后你会注意到Thread 4261以与Thread 1234不同的顺序获取了它的锁,引入了可能的死锁。然后,您需要弄清楚如何修改程序,以便两个线程以相同的顺序获取这些互斥锁...或者更好的是,修改它以便线程不必保持锁定在同一个互斥锁上的多个互斥锁时间到了。

如果你想更进一步,你甚至可以编写一个比较所有序列的函数,并标记其排序代表潜在死锁的函数。如果有大量序列,那么比通过眼睛检测它们更可靠。

FWIW,here是我编写的程序的源代码,用于执行上述的日志文件解析和分析;也许它会有所帮助作为一个例子(或者它可能会让你感到困惑,在这种情况下请忽略它)。