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

Multithreading 如何检测和调试多线程问题?,multithreading,debugging,language-agnostic,Multithreading,Debugging,Language Agnostic,这是一个后续,我没有得到任何关于这一点的意见。以下是一个简短的问题: 是否可以检测和调试来自多线程代码的问题? 通常,我们不得不告诉客户:“我们无法重现问题,因此无法修复它。请告诉我们重现问题的步骤,然后我们将修复它。”如果我知道这是一个多线程问题,这是一个令人讨厌的答案,但大多数情况下我不知道。我如何知道问题是多线程问题以及如何调试它 我想知道是否有任何特殊的日志框架、调试技术、代码检查器或其他帮助解决此类问题的方法。欢迎采取一般办法。如果有任何答案应该与语言相关,那么请使用.NET和Java

这是一个后续,我没有得到任何关于这一点的意见。以下是一个简短的问题:

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

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

我想知道是否有任何特殊的日志框架、调试技术、代码检查器或其他帮助解决此类问题的方法。欢迎采取一般办法。如果有任何答案应该与语言相关,那么请使用.NET和Java。

我认为你的答案非常好。但我要强调这些要点

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

以设定的顺序获取锁,然后以相反的顺序释放锁。

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

此外,一些分析工具可以检测到一些潜在问题。例如,您可以在Java程序中发现一些线程问题。这些工具无法发现所有问题(它们不是万能的),但它们可以提供帮助


正如对此答案的评论中所指出的,研究位置良好的测井输出也非常有用,但要小心。

假设我有难以重现的故障报告,我总是通过阅读代码(最好是成对代码阅读)来发现这些问题,因此您可以讨论线程语义/锁定需求。当我们根据报告的问题来做这件事时,我发现我们总是相当快地解决一个或多个问题。我认为这也是解决难题的一种相当便宜的技术


很抱歉,无法告诉您按ctrl+shift+f13,但我认为没有类似的功能。但是,只要想想报告的问题实际上是什么,代码中通常会有相当强的方向感,因此您不必从main()开始。Visual Studio允许您检查每个线程的调用堆栈,并且可以在它们之间切换。跟踪各种线程问题是远远不够的,但这只是一个开始。即将到来的VS2010计划对多线程调试进行大量改进


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

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

死锁是最容易调试的,如果您可以在死锁时获得堆栈跟踪。考虑到跟踪,其中大部分都进行死锁检测,很容易找到原因,然后找出代码的原因,以及修复原因和方法。对于死锁,以不同的顺序获取相同的锁总是一个问题

活锁更难-在错误状态下观察系统是最好的选择

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

系统越复杂,就越难发现并发错误并对其行为进行推理。使用诸如JVisualVM和remote connect Profiler之类的工具—如果您可以在错误状态下连接到系统并检查线程和对象,它们可以起到救生作用

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


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

除了您已经得到的其他好答案之外:始终在具有至少与客户使用的处理器/处理器内核相同数量的机器上进行测试,或者在您的程序中有活动线程的情况下进行测试。否则,一些多线程错误可能很难重现。

开发代码(不可变对象和Erlang风格的消息传递)。检测多线程问题将更容易,因为线程之间的交互将得到很好的定义。

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

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

有时,您可以在日志文件中看到问题:“此线程正在检测非法/意外状态……请看,另一个线程
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;
}
b func.cpp:2871
r
#c
while (1)
next
#step
end