这是对这个问题的跟进,在这一点上我没有得到任何意见.这是一个简短的问题:
是否可以检测和调试来自多线程代码的问题?
通常我们必须告诉客户:"我们无法在这里重现问题,因此我们无法解决问题.请告诉我们重现问题的步骤,然后我们将解决它." 如果我知道这是一个多线程的问题,这是一个令人讨厌的答案,但大多数情况下我不这样做.我如何才能知道问题是多线程问题以及如何调试它?
我想知道是否有任何特殊的日志框架,调试技术或代码检查器,或其他任何东西来帮助解决这些问题.一般方法是受欢迎的.如果任何答案应该与语言相关,那么请将其保留为.NET和Java.
众所周知,线程/并发问题难以复制 - 这是您应该设计避免或至少最小化概率的原因之一.这就是不可变对象非常有价值的原因.尝试将可变对象隔离到单个线程,然后小心地控制线程之间可变对象的交换.尝试使用对象移交设计进行编程,而不是"共享"对象.对于后者,使用完全同步的控制对象(更容易推理),并避免让同步对象利用必须同步的其他对象 - 也就是说,尝试保持它们自包含.你最好的防守是一个很好的设计.
如果在死锁时可以获得堆栈跟踪,则死锁是最容易调试的.鉴于跟踪,其中大多数执行死锁检测,很容易找出原因,然后推断代码为什么以及如何解决它.对于死锁,在不同的订单中获取相同的锁总是一个问题.
实时锁更难 - 能够观察系统,而处于错误状态是你最好的选择.
竞争条件往往极难复制,甚至更难从手动代码审查中识别出来.有了这些,我通常采取的路径,除了进行大量的复制测试之外,还要推断可能性,并尝试记录信息以证明或反驳理论.如果您有国家腐败的直接证据,您可以根据腐败原因推断可能的原因.
系统越复杂,就越难找到并发错误,并推理出它的行为.利用JVisualVM和远程连接分析器等工具 - 如果您可以连接到处于错误状态的系统并检查线程和对象,它们可以节省生命.
另外,请注意可能行为的差异,这取决于CPU内核,管道,总线带宽等的数量.硬件的变化会影响您复制问题的能力.有些问题只会出现在单核CPU的其他CPU上,而只会出现在多核上.
最后一点,尝试使用随系统库分发的并发对象 - 例如在Java中java.util.concurrent
是你的朋友.编写自己的并发控制对象很难并充满危险; 如果您有选择,请留给专家.
我认为你得到的另一个问题的答案非常好.但我会强调这些观点.
仅修改关键部分中的共享状态(互斥)
按设定顺序获取锁定并以相反的顺序释放它们.
尽可能使用预先构建的抽象(就像java.util.concurrent中的东西一样)
此外,一些分析工具可以检测到一些潜在的问题.例如,FindBugs可以在Java程序中找到一些线程问题.这些工具找不到所有问题(它们不是银子弹),但它们可以提供帮助.
正如vanslly在对这个答案的评论中指出的那样,研究好位置的日志记录输出也非常有用,但要注意Heisenbugs.
假设我有关于难以重现的麻烦的报告,我总是通过阅读代码,最好是对代码读取来找到这些,因此您可以讨论线程语义/锁定需求.当我们根据报告的问题执行此操作时,我发现我们总是很快解决一个或多个问题.我认为这也是解决难题的一种相当便宜的技术.
很抱歉没有能够告诉你按ctrl + shift + f13,但我认为没有任何可用的东西.但只要一想到什么回报的问题实际上是通常给人一种相当强的代码方向感,所以你不必在主开始().
除了已经获得的其他好的答案之外:始终在具有至少与客户使用的处理器/处理器核心数量相同或在程序中有活动线程的机器上进行测试。否则,一些多线程错误可能很难重现。
除了崩溃转储之外,一种技术是广泛的运行时日志记录:每个线程记录它正在做的事情.
报告错误时的第一个问题可能是"日志文件在哪里?"
有时您可以在日志文件中看到问题:"此线程在此处检测到非法/意外状态...并且看,这个其他线程正在执行此操作,就在此之前和/或之后."
如果日志文件没有说明发生了什么,那么向客户道歉,向代码添加足够多的额外日志记录语句,将新代码提供给客户,并说再次发生后再修复它.
对于Java,有一个名为javapathfinder的验证工具,我发现它可以调试和验证多线程应用程序,以防止潜在的竞争条件和死锁错误.
它与Eclipse和Netbean IDE都能很好地协同工作.