繁体   English   中英

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

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

我有一个不是线程安全的类:

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

而且,如果您具有foo1和foo2对象,则在foo2-> anotherFunc()返回之前,不能调用foo1-> someFunc()(这可能在两个线程中发生)。 这是这种情况,不能更改(Foo子类实际上是python脚本的包装器)。

为了防止不必要的通话,我创建了以下内容-

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

在内部,FooWrapper使用共享的互斥体包装对Foo函数的调用。

我想测试FooWrapper的线程安全性。 我最大的问题是线程是由操作系统管理的,这意味着我对其执行的控制较少。 我要测试的是以下情况:

  • 线程1调用fooWrapper1-> someFunc()并在函数内部阻塞
  • 线程2调用fooWrapper2-> anotherFunc()并立即返回(因为someFunc()仍在执行)
  • 线程1完成执行

自动测试这种情况最简单的方法是什么?

我在Win32上使用QT,尽管我更喜欢至少像QT那样跨平台的解决方案。

您可能想要查看CHESS: Microsoft Research的并行软件系统测试工具 它是用于多线程程序(.NET和本机代码)的测试框架。

如果我正确理解的话,它将用它自己的操作系统替换操作系统的线程库,以便它可以控制线程切换。 然后,它对程序进行分析,以找出线程执行流可以进行交织的所有可能方式,并针对每种可能的交织重新运行测试套件。

为什么不创建一个由包装器调用的伪Foo ,而不是仅检查特定线程是否已完成,在该包装中,函数记录了它们实际启动/完成的时间。 然后,您的yield线程只需要等待足够长的时间即可区分记录的时间之间的时差。 在测试中,您可以断言another_func的开始时间在some_func的开始时间之后,并且它的完成时间在some_func的完成时间之前。 由于您的伪造类仅记录时间,因此这足以确保包装器类正常工作。

编辑 :当然,您知道您的Foo对象所做的可能是一个反模式 ,即顺序耦合 根据它的作用,如果尚未调用第一个方法,则仅使第二个方法不执行任何操作就可以处理它。 使用顺序耦合链接中的示例,这类似于如果尚未启动汽车,则在踩下油门踏板时汽车将不执行任何操作。 如果什么都不做是不合适的,则可以等待,然后重试,在当前线程中启动“启动序列”,或将其作为错误处理。 所有这些事情也可以由包装器强制执行,并且可能更易于测试。

如果需要对另一个方法的中间调用,您可能还需要小心确保相同的方法不会被依次两次调用。

英特尔Threadchecker

如果我没记错的话,该工具会在理论上检查您的代码是否存在数据竞争。 关键是您不需要运行代码来检查它是否正确。

当您开始多线程时,根据定义,您的代码将变得不确定,因此在通常情况下,无法进行线程安全性测试。

但是对于您的特定问题,如果您在Foo中插入很长的延迟,导致每种Foo方法花费大量时间,那么您可以执行自己的要求。 即,在第二线程进入调用之前返回的第一线程的概率基本上变为零。

但是,您真正想要实现的目标是什么? 该测试应该测试什么? 如果您尝试验证FooWrappersMutex类是否正常工作,则不会这样做。

到目前为止,我已经编写了以下代码。 有时工作正常,有时测试失败,因为睡眠不足以运行所有线程。

//! 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() );
}

金克斯来营救

http://www.corensic.com/

暂无
暂无

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

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