测试多线程竞争条件


54

阅读对此答案的评论,特别是:

仅仅因为您不能编写测试并不意味着它没有坏。未定义的行为通常会按预期方式起作用(C和C ++充满了),竞争条件,由于内存模型弱而可能重新排序... – CodesInChaos 7小时前

如果无法复制@CodesInChaos,则也无法测试写入“修复”的代码。在我看来,将未经测试的代码付诸实践是更糟糕的罪行– RhysW 5小时前

...让我想知道是否有任何好的通用方法来持续触发由测试用例中的竞争条件引起的生产问题中很少发生的问题。


1
通过两端的指令逐步执行(组装)指令
棘轮怪胎

1
静态分析通常可以显示潜在的UB,但不清楚是否将其计为测试
jk。

抱歉,“ UB”是什么意思?
Doug

2
很好的问题,我将很高兴看到潜在的解决方案。
RhysW 2013年

1
@Doug未定义的行为,包括但不限于比赛条件
jk。

Answers:


85

自1978年左右开始从事这项疯狂的业务后,几乎所有时间都花在嵌入式实时计算,工作多任务,多线程,多系统,有时有多个物理处理器的工作上,这比我所追求的目标还多在这种情况下,我认为您的问题的答案很简单。

没有。

在测试中没有很好的通用方法来触发竞争条件。

您唯一的希望是完全在系统之外进行设计。

如果发现某人塞入其中,则应该将他放到蚁丘外面,然后重新设计以消除它。在系统之外设计了他的人造狗(发音为f *** up)之后,就可以将其从蚂蚁身上释放出来。(如果蚂蚁已经吃掉了他的骨头,只剩下骨头,则张贴一个标语,上面写着“这是将种族条件放入XYZ项目的人会发生的事情!”,然后离开那里。)


22
我完全同意。换句话说,这很像在开玩笑-病人:“医生,当我这样做时会很痛……”医生:“那就别再这样做了!”
Mark Rushakoff 2013年

好答案。如果某些原因导致无法测试的问题,请尝试解决该问题,从根本上避免该问题!
RhysW 2013年

我唯一的问题是:我应该使用多大的蚁丘?(+1 BTW)。
Peter K.

15
+1为faux pas正确发音。(还有其余的答案。)
Blrfl 2013年

1
@PeterK。,这是软件开发中少数几个案例之一,还有显示器,RAM和磁盘驱动器,在这种情况下,IS越大越好。
约翰·斯特罗姆

16

如果您在ms工具链中。她的研究创造了一种工具,该工具将强制每次运行进行新的交互,并可以重新创建失败的运行,称为国际象棋

这是一个视频,显示它的使用情况


5
看起来不错;我将不得不花时间在某个时候尝试一下。
Dan Neely

16

对于这类问题,我所知道的最好的工具是Valgrind的扩展名Helgrind

基本上,Valgrind会模拟一个虚拟处理器,并在其之上运行您的二进制文件(未修改),因此它可以检查对内存的每一次访问。使用该框架,Helgrind监视系统调用以推断何时互斥机制未正确保护对共享变量的访问。这样,即使它实际上没有发生,它也可以检测到理论上的竞争情况。

英特尔出售一种非常相似的工具,称为“ 英特尔检查器”

这些工具可以提供很好的结果,但是您的程序在分析过程中会变慢。


1
Valgrind仍然是仅* nix的工具吗?
Dan Neely 2013年

1
是的,Linux,MacOSX,Android和某些BSD:valgrind.org/info/platforms.html
Julien

1
ThreadSanitizer是一个类似的工具。它的工作方式与Helgrind不同,后者具有更快的优势,但需要集成到工具链中。
塞巴斯蒂安·雷德尔2013年

7

暴露多线程错误需要强制执行的不同线程以特定的交错顺序执行其步骤。通常,如果没有手动调试或操纵代码来获得某种“句柄”来控制这种交错,就很难做到这一点。但是更改行为异常的代码通常会影响这种不可预测性,因此很难实现自动化。

Jaroslav Tulach在《实用API设计》中描述了一个不错的技巧:如果所讨论的代码中包含日志记录语句,请操纵这些日志记录语句的使用者(例如,注入的伪终端),以便它接受特定日志中的各个日志消息。根据其内容排序。这使您可以控制不同线程中步骤的交织,而不必向生产代码中添加尚不存在的任何内容。


2
在使用注入的存储库来休眠按特定顺序调用它的线程以强制进行所需的插入之前,我已经做过类似的事情。编写完成此操作的代码后,我倾向于+1 @@ John的答案。严重的是,这些东西很难正确使用,并且仍然只能提供最佳的猜测保证,因为可能会有稍有不同的交错,但结果不同。更好的方法是通过静态分析和/或仔细组合任何和所有共享状态的代码来消除所有可能的竞争条件
Jimmy Hoffa 2013年

6

无法绝对确定不存在各种未定义的行为(特别是种族条件)。

但是,有许多工具可以显示大量此类情况。即使您不能证明您的修复程序有效,您也可以证明此类工具当前存在问题。

一些用于此目的的有趣工具:

Valgrind是一个内存检查器。它查找内存泄漏,读取未初始化的内存,使用悬空指针和越界访问。

Helgrind是线程安全检查器。它找到比赛条件。

两者都通过动态检测工作,即它们将您的程序保持原样并在虚拟化环境中执行。这使它们不打扰,但速度很慢。

UBSan是未定义的行为检查器。它会发现C和C ++未定义行为的各种情况,例如整数溢出,超出范围的移位和类似的内容。

MSan是内存检查器。它的目标与Valgrind相似。

TSan是线程安全检查器。它的目标与Helgrind相似。

这三个内置在Clang编译器中,并在编译时生成代码。这意味着您需要将它们集成到构建过程中(特别是必须使用Clang进行编译),这使得它们的初始设置比* grind困难得多,但另一方面,它们的运行时开销却要低得多。

我列出的所有工具都可以在Linux上使用,其中一些可以在MacOS上使用。我认为Windows上尚无可靠的工作。


1

似乎大多数答案将这个问题误解为“我如何自动检测比赛条件?” 当问题确实是“找到测试条件时,如何在测试中重现竞赛条件”?

这样做的方法是在仅用于测试的代码中引入同步。例如,如果在事件A和事件B之间发生事件X时发生了竞争情况,则为了测试您的应用程序,编写一些代码,等待事件A发生后事件X发生。您可能需要某种测试方法来与您的应用程序对话以告知它(“嘿,我正在测试此东西,因此请在此位置等待此事件”)。

我正在使用node.js和mongo,其中一些操作涉及在多个集合中创建一致的数据。在这些情况下,我的单元测试将调用应用程序以告知它“设置等待事件X”,一旦应用程序将其设置好,事件X的测试将运行,测试随后将告知应用程序(“我已经完成了等待事件X”),因此其余测试将正常运行。

这里的答案在python上下文中详细解释了这种类型的事情:https : //stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code-可靠地

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.