简体   繁体   English

如何编写自动测试以确保线程安全

[英]How to write an automated test for thread safety

I have a class which is not thread safe: 我有一个不是线程安全的类:

class Foo { 
    /* Abstract base class, code which is not thread safe */ 
};

Moreover, if you have foo1 and foo2 objects, you cannot call foo1->someFunc() until foo2->anotherFunc() has returned (this can happen with two threads). 而且,如果您具有foo1和foo2对象,则在foo2-> anotherFunc()返回之前,不能调用foo1-> someFunc()(这可能在两个线程中发生)。 This is the situation and it can't be changed (a Foo subclass is actually a wrapper for a python script). 这是这种情况,不能更改(Foo子类实际上是python脚本的包装器)。

In order to prevent unwanted calls I've created the following - 为了防止不必要的通话,我创建了以下内容-

class FooWrapper {
public:
    FooWrapper(Foo* foo, FooWrappersMutex* mutex);
    /* Wrapped functions from Foo */
};

Internally, FooWrapper wraps calls to the Foo functions with the shared mutex. 在内部,FooWrapper使用共享的互斥体包装对Foo函数的调用。

I want to test FooWrapper for thread safety. 我想测试FooWrapper的线程安全性。 My biggest problem is the fact that threads are managed by the operating system, which means I've got less control on their execution. 我最大的问题是线程是由操作系统管理的,这意味着我对其执行的控制较少。 What I would like to test is the following scenario: 我要测试的是以下情况:

  • Thread 1 calls fooWrapper1->someFunc() and blocks while inside the function 线程1调用fooWrapper1-> someFunc()并在函数内部阻塞
  • Thread 2 calls fooWrapper2->anotherFunc() and returns immediately (since someFunc() is still executing) 线程2调用fooWrapper2-> anotherFunc()并立即返回(因为someFunc()仍在执行)
  • Thread 1 finishes the execution 线程1完成执行

What is the simplest to test a scenario like this automatically? 自动测试这种情况最简单的方法是什么?

I'm using QT on Win32, although I would prefer a solution which is at least cross-platform as QT is. 我在Win32上使用QT,尽管我更喜欢至少像QT那样跨平台的解决方案。

You might want to check out CHESS: A Systematic Testing Tool for Concurrent Software by Microsoft Research. 您可能想要查看CHESS: Microsoft Research的并行软件系统测试工具 It is a testing framework for multithreaded programs (both .NET and native code). 它是用于多线程程序(.NET和本机代码)的测试框架。

If I understood that correctly, it replaces the operating system's threading libraries with its own, so that it can control thread switching. 如果我正确理解的话,它将用它自己的操作系统替换操作系统的线程库,以便它可以控制线程切换。 Then it analyzes the program to figure out every possible way that the execution streams of the threads can interleave and it re-runs the test suite for every possible interleaving. 然后,它对程序进行分析,以找出线程执行流可以进行交织的所有可能方式,并针对每种可能的交织重新运行测试套件。

Instead of just checking that a particular thread is finished or not, why not create a fake Foo to be invoked by your wrapper in which the functions record the time at which they were actually started/completed. 为什么不创建一个由包装器调用的伪Foo ,而不是仅检查特定线程是否已完成,在该包装中,函数记录了它们实际启动/完成的时间。 Then your yield thread need only wait long enough to be able to distinguish the difference between the recorded times. 然后,您的yield线程只需要等待足够长的时间即可区分记录的时间之间的时差。 In your test you can assert that another_func 's start time is after some_func 's start time and it's completed time is before some_func s completed time. 在测试中,您可以断言another_func的开始时间在some_func的开始时间之后,并且它的完成时间在some_func的完成时间之前。 Since your fake class is only recording the times, this should be sufficient to guarantee that the wrapper class is working properly. 由于您的伪造类仅记录时间,因此这足以确保包装器类正常工作。

EDIT : You know, of course, that what your Foo object does could be an anti-pattern , namely Sequential Coupling . 编辑 :当然,您知道您的Foo对象所做的可能是一个反模式 ,即顺序耦合 Depending what it does, you may be able to handle it by simply having the second method do nothing if the first method has not yet been called. 根据它的作用,如果尚未调用第一个方法,则仅使第二个方法不执行任何操作就可以处理它。 Using the example from the Sequential Coupling link, this would be similar to having the car do nothing when the accelerator pedal is pressed, if the car has not yet been started. 使用顺序耦合链接中的示例,这类似于如果尚未启动汽车,则在踩下油门踏板时汽车将不执行任何操作。 If doing nothing is not appropriate, you could either wait and try again later, initiate the "start sequence" in the current thread, or handle it as an error. 如果什么都不做是不合适的,则可以等待,然后重试,在当前线程中启动“启动序列”,或将其作为错误处理。 All of these things could be enforced by your wrapper as well and would probably be easier to test. 所有这些事情也可以由包装器强制执行,并且可能更易于测试。

You also may need to be careful to make sure that the same method doesn't get invoked twice in sequence if an intervening call to another method is required. 如果需要对另一个方法的中间调用,您可能还需要小心确保相同的方法不会被依次两次调用。

Intel Threadchecker . 英特尔Threadchecker

If I recall correctly the tool checks your code for theoretically possible data races. 如果我没记错的话,该工具会在理论上检查您的代码是否存在数据竞争。 The point is you don't need to run your code to check whether it's correct or not. 关键是您不需要运行代码来检查它是否正确。

When you start multithreading, your code becomes, by definition, non-deterministic, so testing for thread safety is, in the general case, impossible. 当您开始多线程时,根据定义,您的代码将变得不确定,因此在通常情况下,无法进行线程安全性测试。

But to your very specific question, if you insert long delays inside Foo to cause each Foo method to take a looonnng time, then you can do what you ask. 但是对于您的特定问题,如果您在Foo中插入很长的延迟,导致每种Foo方法花费大量时间,那么您可以执行自己的要求。 That is, the probablity of the first thread returning before the second thread enters the call becomes essentially zero. 即,在第二线程进入调用之前返回的第一线程的概率基本上变为零。

But what is it that you're really trying to accomplish? 但是,您真正想要实现的目标是什么? What is this test supposed to test? 该测试应该测试什么? If you trying to validate that the FooWrappersMutex class works correctly, this won't do it. 如果您尝试验证FooWrappersMutex类是否正常工作,则不会这样做。

So far I've written the following code. 到目前为止,我已经编写了以下代码。 Sometimes it works and sometimes the test fails, since the Sleep is not enough for running all threads. 有时工作正常,有时测试失败,因为睡眠不足以运行所有线程。

//! Give some time to the other threads
static void YieldThread()
{
#ifdef _WIN32
    Sleep(10);
#endif //_WIN32
}

class FooWithMutex: public Foo {
public:
    QMutex m_mutex;
    virtual void someFunc()
    {
        QMutexLocker(&m_mutex);
    }
    virtual void anotherFunc()
    {
        QMutexLocker(&m_mutex);
    }
};

class ThreadThatCallsFooFunc1: public QThread {
public:
    ThreadThatCallsFooFunc1( FooWrapper& fooWrapper )
        : m_fooWrapper(fooWrapper) {}

    virtual void run()
    {
        m_fooWrapper.someFunc();
    }
private:
    FooWrapper& m_fooWrapper;
};

class ThreadThatCallsFooFunc2: public QThread {
public:
    ThreadThatCallsFooFunc2( FooWrapper& fooWrapper )
        : m_fooWrapper(fooWrapper) {}

    virtual void run()
    {
        m_fooWrapper.anotherFunc();
    }
private:
    FooWrapper& m_fooWrapper;
};

TEST(ScriptPluginWrapperTest, CallsFromMultipleThreads)
{
    // FooWithMutex inherits the abstract Foo and adds
    // mutex lock/unlock on each function.
    FooWithMutex fooWithMutex;

    FooWrapper fooWrapper( &fooWithMutex );
    ThreadThatCallsFooFunc1 thread1(fooWrapper);
    ThreadThatCallsFooFunc2 thread2(fooWrapper);

    fooWithMutex.m_mutex.lock();
    thread1.start(); // Should block

    YieldThread();
    ASSERT_FALSE( thread1.isFinished() );

    thread2.start(); // Should finish immediately
    YieldThread();
    ASSERT_TRUE( thread2.isFinished() );

    fooWithMutex.m_mutex.unlock();

    YieldThread();
    EXPECT_TRUE( thread1.isFinished() );
}

Jinx to the rescue 金克斯来营救

http://www.corensic.com/ http://www.corensic.com/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM