代码之家  ›  专栏  ›  技术社区  ›  MicSim

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

  •  59
  • MicSim  · 技术社区  · 15 年前

    这是一个后续行动 this question ,在这一点上我没有得到任何意见。以下是一个简短的问题:

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

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

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

    16 回复  |  直到 7 年前
        1
  •  90
  •   L. Cornelius Dol    4 年前

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

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

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

    比赛条件

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

    最后一件事,尝试使用与系统库一起分布的并发对象-例如在Java中 java.util.concurrent 他是你的朋友。

        2
  •  7
  •   Community Egal    7 年前

    我认为 answer other question 很好。但我要强调这些要点。

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

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

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

    FindBugs 可以在Java程序中找到一些线程问题。这些工具无法发现所有问题(它们不是万能的),但它们可以提供帮助。

    vanslly 在对这个答案的评论中指出,研究井位测井输出也很有帮助,但要小心 Heisenbugs .

        3
  •  6
  •   bLaXjack    5 年前

    javapathfinder
    它可以很好地与Eclipse和Netbean IDE配合使用。

    [2019]github存储库 https://github.com/javapathfinder

        4
  •  5
  •   krosenvold    15 年前

    报告的问题 ,我发现我们总是相当快地解决一个或多个问题。我认为这也是解决难题的一种相当便宜的技术。

    什么 通常在代码中提供相当强的方向感,因此不必从main()开始。

        5
  •  5
  •   mghie    15 年前

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

        6
  •  5
  •   ChrisW    15 年前

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

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

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

        7
  •  3
  •   Peter Huber    10 年前

    1. 不要使用任何锁
    2. 多线程安全吗

    这听起来像是一项不可能完成的任务,但可以通过将跟踪写入内存来轻松实现。在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

        8
  •  2
  •   Mouze    9 年前

    图表正在增长,请留下要添加的评论和提示。 (更新文件地址: this link )

    Multithreaded debugging chart

        9
  •  1
  •   Brian Rasmussen    15 年前

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

        10
  •  1
  •   Sean    15 年前

    Tess Ferrandez's blog 有使用WinDbg调试.NET死锁的好例子。

        11
  •  1
  •   zvrba    15 年前

        12
  •  1
  •   Thomas Krieger    10 年前

    我实现了这个工具 vmlens eraser .

        14
  •  0
  •   Kuldeep Tiwari    11 年前

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

    根据我的经验,我可以说最难的部分是认识到这是一个线程问题,而最好的解决方案是仔细检查多线程代码。通过仔细查看线程代码,您应该尝试找出可能出现的错误。其他方式(线程转储、分析器等)将排在第二位。

        15
  •  0
  •   John Smith jjcaicedo    5 年前

    缩小正在调用的函数的范围,并排除可能和不可能的责任。当您发现您怀疑可能导致问题的代码部分时,请添加大量详细的日志记录/跟踪。一旦问题再次出现,请检查日志,查看代码的执行方式与“基线”情况下的不同。

    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

        16
  •  -2
  •   BNR    7 年前

    $more gdb_跟踪程序

    b func.cpp:2871
    r
    #c
    while (1)
    next
    #step
    end
    
        17
  •  -10
  •   max    15 年前

    我能想到的最好的办法就是尽可能远离多线程代码。似乎很少有程序员能够编写无bug的多线程应用程序,我认为没有程序员能够编写无bug的多线程应用程序 大的