到目前为止,我已经避免了测试多线程代码的噩梦,因为它似乎是一个太大的雷区。我想知道人们是如何测试依赖于线程的代码以获得成功执行的,或者人们是如何测试那些仅在两个线程以给定方式交互时才会出现的问题的?

对于今天的程序员来说,这似乎是一个非常关键的问题,恕我直言,将我们的知识集中在这个问题上是很有用的。


当前回答

并发是内存模型、硬件、缓存和代码之间复杂的相互作用。在Java的情况下,至少这样的测试主要由jcstress部分解决。众所周知,该库的创建者是许多JVM、GC和Java并发特性的作者。

但是即使是这个库也需要对Java内存模型规范有很好的了解,这样我们才能确切地知道我们在测试什么。但我认为这项工作的重点是微基准测试。不是庞大的业务应用。

其他回答

(如果可能的话)不要使用线程,使用actor /活动对象。易于测试。

我喜欢编写两个或多个测试方法在并行线程上执行,并且每个方法都调用被测对象。我一直在使用Sleep()调用来协调来自不同线程的调用顺序,但这并不真正可靠。它也慢得多,因为你必须睡足够长的时间,时间通常是有效的。

我从编写FindBugs的同一组中找到了多线程TC Java库。它允许您在不使用Sleep()的情况下指定事件的顺序,而且它是可靠的。我还没试过。

这种方法的最大限制是它只允许您测试您怀疑会引起麻烦的场景。正如其他人所说,您确实需要将多线程代码隔离到少量简单类中,以便有希望彻底测试它们。

一旦您仔细测试了您预计会导致问题的场景,那么在类中抛出一堆并发请求的不科学测试是寻找意外问题的好方法。

更新:我已经玩了一些多线程TC Java库,它工作得很好。我还将它的一些特性移植到一个。net版本,我称之为TickingTest。

有一篇关于这个主题的文章,在示例代码中使用Rust作为语言:

https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a

总而言之,诀窍在于编写并发逻辑,使其对涉及多个执行线程的非确定性具有健壮性,使用通道和condvars等工具。

然后,如果这就是您构建“组件”的方式,那么测试它们的最简单方法是使用通道向它们发送消息,然后阻塞其他通道以断言组件发送某些预期的消息。

链接到的文章完全使用单元测试编写。

下面的文章提出了两种解决方案。包装一个信号量(CountDownLatch),并添加诸如从内部线程外部化数据之类的功能。实现此目的的另一种方法是使用线程池(请参阅兴趣点)。

喷头-高级同步对象

一个简单的测试模式可以用于一些(不是所有!)用例,就是多次重复相同的测试。例如,假设你有一个方法:

def process(input):
    # Spawns several threads to do the job
    # ...
    return output

创建一堆测试:

process(input1) -> expect to return output1
process(input2) -> expect to return output2
...

现在将每个测试运行多次。

如果流程的实现包含一个微小的错误(例如死锁、竞态条件等),出现的概率为0.1%,那么运行1000次测试,则该错误至少出现一次的概率为64%。运行测试10000次,得到>99%的概率。