[英]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的线程安全性。 我最大的问题是线程是由操作系统管理的,这意味着我对其执行的控制较少。 我要测试的是以下情况:
自动测试这种情况最简单的方法是什么?
我在Win32上使用QT,尽管我更喜欢至少像QT那样跨平台的解决方案。
您可能想要查看CHESS: Microsoft Research的并行软件系统测试工具 。 它是用于多线程程序(.NET和本机代码)的测试框架。
如果我正确理解的话,它将用它自己的操作系统替换操作系统的线程库,以便它可以控制线程切换。 然后,它对程序进行分析,以找出线程执行流可以进行交织的所有可能方式,并针对每种可能的交织重新运行测试套件。
为什么不创建一个由包装器调用的伪Foo
,而不是仅检查特定线程是否已完成,在该包装中,函数记录了它们实际启动/完成的时间。 然后,您的yield线程只需要等待足够长的时间即可区分记录的时间之间的时差。 在测试中,您可以断言another_func
的开始时间在some_func
的开始时间之后,并且它的完成时间在some_func
的完成时间之前。 由于您的伪造类仅记录时间,因此这足以确保包装器类正常工作。
编辑 :当然,您知道您的Foo
对象所做的可能是一个反模式 ,即顺序耦合 。 根据它的作用,如果尚未调用第一个方法,则仅使第二个方法不执行任何操作就可以处理它。 使用顺序耦合链接中的示例,这类似于如果尚未启动汽车,则在踩下油门踏板时汽车将不执行任何操作。 如果什么都不做是不合适的,则可以等待,然后重试,在当前线程中启动“启动序列”,或将其作为错误处理。 所有这些事情也可以由包装器强制执行,并且可能更易于测试。
如果需要对另一个方法的中间调用,您可能还需要小心确保相同的方法不会被依次两次调用。
如果我没记错的话,该工具会在理论上检查您的代码是否存在数据竞争。 关键是您不需要运行代码来检查它是否正确。
当您开始多线程时,根据定义,您的代码将变得不确定,因此在通常情况下,无法进行线程安全性测试。
但是对于您的特定问题,如果您在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() );
}
金克斯来营救
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.