如何检测和调试多线程问题?

时间:2009-01-31 21:37:41

标签: multithreading debugging language-agnostic

这是对this question的跟进,在这一点上我没有得到任何意见。这是一个简短的问题:

是否可以检测和调试来自多线程代码的问题?

通常我们必须告诉客户:“我们无法在此处重现问题,因此我们无法解决问题。请告诉我们重现问题的步骤,然后我们将解决它。”如果我知道这是一个多线程的问题,这是一个令人讨厌的答案,但大多数情况下我不这样做。我如何才能知道问题是一个多线程问题以及如何调试它?

我想知道是否有任何特殊的日志记录框架,调试技术或代码检查器,或其他任何有助于解决此类问题的内容。一般方法是受欢迎的。如果任何答案应与语言相关,那么请将其保留为.NET和Java。

17 个答案:

答案 0 :(得分:77)

线程/并发问题众所周知难以复制 - 这是您应该设计避免或至少最小化概率的原因之一。这就是不可变对象非常有价值的原因。尝试将可变对象隔离到单个线程,然后小心地控制线程之间可变对象的交换。尝试使用对象移交设计进行编程,而不是“共享”对象。对于后者,使用完全同步的控制对象(更容易推理),并避免让同步对象利用必须同步的其他对象 - 也就是说,尝试保持它们自包含。你最好的防守是一个很好的设计。

死锁是最容易调试的,如果您在死锁时可以获得堆栈跟踪。鉴于跟踪,其中大多数执行死锁检测,很容易找出原因,然后推断代码为什么以及如何解决它。对于死锁,在不同的订单中获取相同的锁总是一个问题。

实时锁更难 - 能够在错误状态下观察系统是最好的选择。

竞争条件往往难以复制,甚至更难从手动代码审核中识别出来。有了这些,我通常采取的路径,除了进行大量的复制测试之外,还要推断可能性,并尝试记录信息以证明或反驳理论。如果您有国家腐败的直接证据,您可以根据腐败来推断可能的原因。

系统越复杂,就越难找到并发错误,并推断出它的行为。利用JVisualVM和远程连接分析器等工具 - 如果您可以连接到处于错误状态的系统并检查线程和对象,它们可以节省生命。

另外,请注意可能行为的差异,这取决于CPU内核,管道,总线带宽等的数量。硬件的变化会影响您复制问题的能力。有些问题只会出现在单核CPU的其他CPU上,而只会出现在多核上。

最后一件事,尝试使用随系统库分发的并发对象 - 例如在Java java.util.concurrent中是您的朋友。 编写自己的并发控制对象很难并充满危险;如果您有选择,请留给专家。

答案 1 :(得分:7)

我认为你answerother question非常好。但我会强调这些观点。

仅修改关键部分中的共享状态(互斥)

以固定顺序获取锁定并以相反的顺序释放它们。

尽可能使用预先构建的抽象(与java.util.concurrent中的内容类似)

此外,一些分析工具可以检测到一些潜在的问题。例如,FindBugs可以在Java程序中找到一些线程问题。这些工具找不到所有问题(它们不是银子弹),但它们可以提供帮助。

正如vanslly在对此答案的评论中指出的那样,研究好位置的日志记录输出也非常有用,但要注意Heisenbugs

答案 2 :(得分:5)

假设我有关于难以重现的麻烦的报告,我总是通过阅读代码,最好是对代码读取来找到这些,因此您可以讨论线程语义/锁定需求。当我们根据报告的问题执行此操作时,我发现我们总是很快解决一个或多个问题。我认为解决难题也是一种相当便宜的技术。

很抱歉无法告诉你按ctrl + shift + f13,但我认为没有任何可用的东西。但是只考虑 报告的问题 通常会在代码中给出相当强烈的方向感,因此您不必从main()开始。< / p>

答案 3 :(得分:5)

除了您已经获得的其他好答案之外:始终在至少与客户使用的处理器/处理器核心一样多的计算机上进行测试,或者在程序中存在活动线程。否则,一些多线程错误可能很难重现。

答案 4 :(得分:5)

除了崩溃转储之外,一种技术是广泛的运行时日志记录:每个线程记录它正在做的事情。

报告错误时的第一个问题可能是“日志文件在哪里?”

有时你可以在日志文件中看到问题:“这个线程在这里检测到非法/意外状态......看看,这个其他线程正在这样做,就在此之前和/或之后。”

如果日志文件没有说明发生了什么,那么向客户道歉,向代码中添加足够多的额外日志记录语句,将新代码提供给客户,并说明它会在发生后修复再一次。

答案 5 :(得分:5)

对于Java,有一个名为javapathfinder的验证工具,我发现它有助于调试和验证多线程应用程序以防止潜在的竞争条件和代码中的死锁错误。
它与Eclipse和Netbean IDE都能很好地协同工作。

答案 6 :(得分:3)

有时,多线程解决方案无法避免。如果存在错误,则需要实时调查,这对于Visual Studio等大多数工具来说几乎是不可能的。唯一可行的解​​决方案是编写跟踪,尽管跟踪本身应该:

  1. 不添加任何延迟
  2. 不使用任何锁定
  3. 多线程安全
  4. 以正确的顺序跟踪发生的事情。
  5. 这听起来像是一项不可能完成的任务,但可以通过将跟踪写入内存来轻松实现。在C#中,它看起来像这样:

    public const int MaxMessages = 0x100;
    string[] messages = new string[MaxMessages];
    int messagesIndex = -1;
    
    public void Trace(string message) {
      int thisIndex = Interlocked.Increment(ref messagesIndex);
      messages[thisIndex] = message;
    }
    

    方法Trace()是多线程安全的,非阻塞的,可以从任何线程调用。在我的电脑上,执行大约需要2微秒,这应该足够快。

    在您认为可能出错的地方添加Trace()指令,让程序运行,等到错误发生,停止跟踪,然后调查跟踪是否有任何错误。

    此方法的更详细说明,它还收集线程和计时信息,循环缓冲区并输出跟踪,您可以在以下位置找到: CodeProject:实时调试多线程代码1

答案 7 :(得分:2)

在调试多线程代码时需要考虑一些调试技术的小图表。 图表正在增长,请留下评论和提示添加。 (更新档案this link

Multithreaded debugging chart

答案 8 :(得分:1)

Visual Studio允许您检查每个线程的调用堆栈,并且可以在它们之间切换。追踪各种线程问题并不足够,但这是一个开始。对于即将推出的VS2010,计划对多线程调试进行大量改进。

我使用WinDbg + SoS来解决.NET代码中的线程问题。您可以检查锁(同步blokcs),线程调用堆栈等。

答案 9 :(得分:1)

Tess Ferrandez's blog有很好的例子,可以使用WinDbg来调试.NET中的死锁。

答案 10 :(得分:1)

assert()是您检测竞争条件的朋友。每当你进入一个临界区时,断言与之关联的不变量是真的(这就是CS的用途)。不幸的是,检查可能很昂贵,因此不适合在生产环境中使用。

答案 11 :(得分:1)

我实现了工具vmlens以在运行时检测java程序中的竞争条件。它实现了一个名为eraser的算法。

答案 12 :(得分:0)

缩小正在调用的函数,并排除应归咎于什么。当您发现怀疑可能导致该问题的代码段时,请向其添加大量详细的日志记录/跟踪。问题再次发生后,请检查日志以查看代码执行情况与“基准”情况下的执行情况不同。

如果使用的是Visual Studio,还可以设置断点并使用“并行堆栈”窗口。并行堆栈在调试并发代码时是一个巨大的帮助,它将使您能够在线程之间切换以独立调试它们。更多信息-

https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-parallel-stacks-window?view=vs-2019

https://docs.microsoft.com/en-us/visualstudio/debugger/walkthrough-debugging-a-parallel-application?view=vs-2019

答案 13 :(得分:0)

我遇到了一个线程问题,这个问题导致了SAME错误的结果,并且由于每次其他条件(内存,调度程序,处理负载)或多或少相同,因此不会出现不可预测的行为。

根据我的经验,我可以说最困难的部分是认识到它是一个线程问题,而BEST SOLUTION是仔细检查多线程代码。只要仔细查看线程代码,您应该尝试找出可能出错的地方。其他方式(线程转储,分析器等)将成为它的第二位。

答案 14 :(得分:0)

开发代码the way that Princess recommended for your other question(不可变对象和Erlang样式的消息传递)。检测多线程问题会更容易,因为线程之间的交互将得到很好的定义。

答案 15 :(得分:-2)

我正在使用GNU并使用简单的脚本

$ more gdb_tracer

b func.cpp:2871
r
#c
while (1)
next
#step
end

答案 16 :(得分:-9)

我能想到的最好的事情就是尽可能远离多线程代码。似乎很少有程序员可以编写无错误的多线程应用程序,我认为没有编程人员可以编写无错误的大型多线程应用程序。