在单元测试中确定线程安全性

时间:2012-02-27 16:48:16

标签: c# .net multithreading unit-testing

我正在编写一个多线程应用程序,并试图找出如何为它编写单元测试。我认为这可能是另一个问题,关于如何最好地做到这一点。无论如何,我有一个类似下面的类知道它不是线程安全的,并且想要在单元测试中证明它但是无法解决如何做到这一点:

public class MyClass
{
    private List<string> MyList = new List<string>();

    public void Add(string Data)
    {
        MyList.Add(Data);  //This is not thread safe!!
    }
}

5 个答案:

答案 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);

希望这对某人有帮助。