如何检测和debuggingmultithreading问题?
这是这个问题的后续,在这一点上我没有得到任何意见。 这里是一个简单的问题:
是否有可能检测和debugging来自multithreading代码的问题?
通常我们必须告诉我们的客户:“我们不能在这里重现问题,所以我们不能修复它,请告诉我们重现问题的步骤,然后我们将解决它。 如果我知道这是一个multithreading问题,但是大多数情况下我不知道这是一个不知何故的答案。 我怎样才能知道一个问题是一个multithreading问题,以及如何debugging?
我想知道是否有任何特殊的日志框架,debugging技术或代码检查器,或其他任何东西来帮助解决这些问题。 一般方法是受欢迎的。 如果有任何答案应该是语言相关的,那么把它保持到.NET和Java。
众所周知, 线程/并发问题很难复制 – 这就是为什么您应该devise避免或至less是最小化概率的原因之一。 这就是不可变对象如此宝贵的原因。 尝试将可变对象分离到单个线程,然后仔细控制线程之间的可变对象的交换。 尝试使用对象移交的devise进行编程,而不是“共享”对象。 对于后者,使用完全同步的控制对象(这是更容易推理的),并避免有一个同步的对象利用其他对象,这些对象也必须同步 – 也就是试图保持它们自包含。 你最好的防守是一个很好的devise。
死锁是最容易debugging,如果你可以得到死锁时的堆栈跟踪。 鉴于踪迹,其中大部分进行死锁检测,很容易查明原因,然后推理为什么以及如何解决这个问题。 随着死锁,总是会有一个问题,以不同的顺序获取相同的锁。
活锁更难 – 能够在错误状态下观察系统是您最好的select。
种族条件往往是非常难以复制,更难以从手工代码审查识别。 有了这些,我通常所采取的path,除了广泛的testing复制之外,就是推断可能性,并尝试logging信息来certificate或反驳理论。 如果你有直接的国家腐败证据,你可以根据腐败来推断可能的原因。
系统越复杂,发现并发错误就越困难,并推断其行为。 使用像JVisualVM和远程连接分析器这样的工具 – 如果您可以连接到处于错误状态的系统并检查线程和对象,则它们可以成为救命稻草。
此外,请注意可能行为的差异,这些差异取决于CPU内核数量,stream水线数量,总线带宽等。硬件变化可能会影响您复制问题的能力。 一些问题只会出现在单核CPU的其他部分,只能在多核上。
最后一件事,尝试使用与系统库一起分发的并发对象 – 例如在Java java.util.concurrent
是你的朋友。 编写自己的并发控制对象很困难,充满危险。 如果你有select的话,请留给专家处理。
我认为你对另一个问题的答案相当好。 但是我会强调这些观点。
只在关键部分修改共享状态(互斥)
以设置的顺序获取锁,然后以相反的顺序释放它们。
尽可能使用预先构build的抽象 (像java.util.concurrent中的东西)
另外,一些分析工具可以检测到一些潜在的问题。 例如, FindBugs可以在Java程序中find一些线程问题。 这样的工具找不到所有的问题(他们不是银弹),但他们可以帮助。
正如vanslly在对这个答案的评论中指出的那样,研究放置好的日志输出也是非常有帮助的,但要小心Heisenbugs 。
除了崩溃转储之外,一项技术是广泛的运行时日志logging:每个线程logging它正在做什么。
那么报告错误时的第一个问题可能是:“日志文件在哪里?
有时候你可以在日志文件中看到这个问题:“这个线程正在检测一个非法/意外的状态,看看,这个线程正在这样做,就在这之前和/或之后。
如果日志文件没有说明发生了什么,那就向客户道歉,在代码中添加足够多的额外的日志语句,把新的代码交给客户,并且说在发生一次后再修复它。
对于Java,有一个名为javapathfinder的validation工具,我发现它有助于debugging和validationmultithreading应用程序,以防止代码中潜在的竞争条件和死锁错误。
它与Eclipse和Netbean IDE都能正常工作。
假设我有难以复制的麻烦的报告,我总是通过阅读代码来find这些问题,最好是对代码阅读,所以你可以讨论线程语义/locking需求。 当我们根据所报告的问题来做这件事情时,我发现我们总是相当快地找出一个或多个问题。 我认为解决难题也是一个相当便宜的技术。
对不起,不能告诉你按Ctrl + Shift + F13,但我不认为有这样的事情可用。 但是考虑一下实际报告的问题通常会给代码带来相当强烈的方向感,所以你不必从main()开始。
除了你已经得到的其他好的答案之外:总是在至less与客户使用的处理器/处理器内核一样多的机器上testing,或者在程序中有活动的线程。 否则,某些multithreading错误可能难以复制。
有时候,multithreading解决scheme是无法避免的。 如果存在缺陷,则需要实时进行调查,而像Visual Studio这样的大多数工具几乎是不可能的。 唯一实际的解决办法是写痕迹,尽pipe追踪本身应该:
- 不加延迟
- 不使用任何locking
- multithreading安全
- 跟踪正确顺序中发生的事情。
这听起来像一个不可能完成的任务,但是通过将轨迹写入内存很容易。 在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()方法是multithreading安全的,非阻塞的,可以从任何线程调用。 在我的PC上,执行需要大约2微秒,这应该足够快。
在你认为出现问题的地方添加Trace()指令,让程序运行,等到错误发生,停止跟踪,然后调查跟踪任何错误。
这个方法的更详细的描述,它也收集线程和时间信息,回收缓冲区,并输出很好的轨迹,你可以find:CodeProject:实时debuggingmultithreading代码1
Visual Studio允许您检查每个线程的调用堆栈,并且可以在它们之间切换。 跟踪各种线程问题是不够的,但这是一个开始。 计划在即将到来的VS2010上进行multithreadingdebugging。
我已经使用WinDbg + SoS在.NET代码中的线程问题。 你可以检查锁(同步blokcs),线程调用堆栈等
Tess Ferrandez的博客有很好的使用WinDbgdebugging.NET中的死锁的例子。
断言()是你的朋友检测竞争条件。 每当你进入临界区时,断言与之相关的不variables是真的(这就是CS的用途)。 虽然不幸的是,检查可能是昂贵的,因此不适合在生产环境中使用。
我实现了工具vmlens来在运行时检测Java程序中的竞争条件。 它实现了一个叫做橡皮擦的algorithm。
在debuggingmultithreading代码时需要注意一些debugging技巧。 图表正在增长,请留下评论和提示添加。 (更新文件在这个链接 )
开发代码公主推荐你的其他问题 (不可变对象,和Erlang式消息传递)的方式。 检测multithreading问题会更容易,因为线程之间的交互将被很好地定义。
我遇到了一个线程问题,这个问题给了SAME错误的结果,并且因为每次其他条件(内存,调度程序,处理负载)差不多都是相同的。
根据我的经验,我可以说HARDEST PART是认识到这是一个线程问题,BEST SOLUTION是仔细查看multithreading代码。 只需仔细查看线程代码,就可以找出可能出错的地方。 其他方式(线程转储,分析器等)将排在第二位。
我正在使用GNU并使用简单的脚本
$ more gdb_tracer
b func.cpp:2871 r #c while (1) next #step end
我能想到的最好的事情是尽可能远离multithreading代码。 看起来有很less的程序员可以编写无bug的multithreading应用程序,我认为没有编程人员可以编写无bug的大型multithreading应用程序。