我正在编写一个多线程应用程序,并试图找出如何为它编写单元测试。我认为这可能是另一个问题,关于如何最好地做到这一点。无论如何,我有一个类似下面的类知道它不是线程安全的,并且想要在单元测试中证明它但是无法解决如何做到这一点:
public class MyClass
{
private List<string> MyList = new List<string>();
public void Add(string Data)
{
MyList.Add(Data); //This is not thread safe!!
}
}
答案 0 :(得分:12)
证明某些东西是线程安全的是棘手的 - 可能很难解决问题。您可以证明竞争条件很容易产生,或者很难生产。但不产生竞争条件并不意味着它不存在。
但是:我在这里常用的方法(如果我有理由认为应该是线程安全的一些代码,那么)就是在一个ManualResetEvent后面等待很多线程。到达门的最后一个线程(使用interlocked to count)负责打开门,以便所有线程同时(并且已经存在)命中系统。然后他们完成工作并检查理智的退出条件。然后我重复这个过程很多次。这通常足以重现一个可疑的线程,并表明它从“明显破碎”变为“未明显破坏”(这与“未破坏”至关重要)。
另请注意:大多数代码不必是线程安全的。
答案 1 :(得分:7)
我经常编写单元测试来证明某些代码是线程安全的。通常,我会编写这些测试以响应生产中发现的错误。在这种情况下,测试的目的是证明错误被复制(测试失败),并且新代码修复了线程问题(测试通过),然后充当未来版本的回归测试。
我编写的大多数测试线程安全测试都测试了一个线程争用情况,但有些还测试了线程死锁。
主动测试代码 线程安全的单元测试有点棘手。不是因为单元测试更难写,而是因为你必须进行可靠的分析以确定(猜测,真的)可能是线程不安全的。如果您的分析是正确的,那么您应该能够编写一个失败的测试,直到您使代码线程安全为止。
在测试线程竞争条件时,我的测试几乎总是遵循相同的模式:(这是伪代码)
boolean failed = false;
int iterations = 100;
// threads interact with some object - either
Thread thread1 = new Thread(new ThreadStart(delegate() {
for (int i=0; i<iterations; i++) {
doSomething(); // call unsafe code
// check that object is not out of synch due to other thread
if (bad()) {
failed = true;
}
}
});
Thread thread2 = new Thread(new ThreadStart(delegate() {
for (int i=0; i<iterations; i++) {
doSomething(); // call unsafe code
// check that object is not out of synch due to other thread
if (bad()) {
failed = true;
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
Assert.IsFalse(failed, "code was thread safe");
答案 2 :(得分:4)
线程安全不是你可以可靠测试的东西,因为它本质上是非确定性的。您可以尝试在不同的线程上并行运行相同的操作几百次,并查看结果是否一致。不是很好,但我认为总比没有好。
答案 3 :(得分:3)
我已经放弃了单元测试来尝试检测线程问题。我只是使用一整套硬件进行系统负载测试(如果有硬件 - 通常是我的工作,网络上的uControllers),以及不合理的大量自动化客户端运行平稳。当我做其他事情时,我让它运行一周。在一周结束时,我将负载关闭并检查剩下的内容。如果它仍在工作,没有物体泄漏,内存没有明显增加,我发货。
那是我能买得起的那么多的质量:)
答案 4 :(得分:0)
我遇到了类似的问题,我们发现了线程安全性错误。要修复它,我们必须先证明它,然后再修复它。该任务将我带到了此页面,但我找不到真正的答案。正如以上许多答案所解释的那样。但是,我仍然找到了可以帮助他人的可能方法:
public static async Task<(bool IsSuccess, Exception Error)> RunTaskInParallel(Func<Task> task, int numberOfParallelExecutions = 2)
{
var cancellationTokenSource = new CancellationTokenSource();
Exception error = null;
int tasksCompletedCount = 0;
var result = Parallel.For(0, numberOfParallelExecutions, GetParallelLoopOptions(cancellationTokenSource),
async index =>
{
try
{
await task();
}
catch (Exception ex)
{
error = ex;
cancellationTokenSource.Cancel();
}
finally
{
tasksCompletedCount++;
}
});
int spinWaitCount = 0;
int maxSpinWaitCount = 100;
while (numberOfParallelExecutions > tasksCompletedCount && error is null && spinWaitCount < maxSpinWaitCount))
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
spinWaitCount++;
}
return (error == null, error);
}
这不是最干净的代码,也不是我们最终的结果,但逻辑保持不变。这段代码每次都证明了我们的线程安全性错误。
这是我们的用法:
int numberOfParallelExecutions = 2;
RunTaskInParallel(() => doSomeThingAsync(), numberOfParallelExecutions);
希望这对某人有帮助。