【问题标题】:How to write an automated test for thread safety如何编写线程安全的自动化测试
【发布时间】:2010-09-28 12:53:18
【问题描述】:

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

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

此外,如果你有 foo1 和 foo2 对象,你不能调用 foo1->someFunc() 直到 foo2->anotherFunc() 返回(这可能发生在两个线程上)。就是这种情况,无法更改(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 那样跨平台的解决方案。

【问题讨论】:

    标签: c++ multithreading unit-testing testing


    【解决方案1】:

    您可能想查看 Microsoft Research 的 CHESS: A Systematic Testing Tool for Concurrent Software。它是用于多线程程序(.NET 和本机代码)的测试框架。

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

    【讨论】:

      【解决方案2】:

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

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

      如果需要对另一个方法的介入调用,您可能还需要小心确保不会按顺序调用同一方法两次。

      【讨论】:

        【解决方案3】:

        Intel Threadchecker.

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

        【讨论】:

        • Here的更新链接
        【解决方案4】:

        当您开始使用多线程时,根据定义,您的代码变得不确定,因此在一般情况下,测试线程安全是不可能的。

        但是对于您非常具体的问题,如果您在 Foo 中插入长时间延迟以导致每个 Foo 方法花费很长时间,那么您可以按照您的要求做。也就是说,第一个线程在第二个线程进入调用之前返回的概率基本上为零。

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

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

            金克斯来救援

            http://www.corensic.com/

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-01-01
              • 1970-01-01
              • 2011-09-21
              • 2010-10-31
              • 1970-01-01
              • 2019-03-19
              • 1970-01-01
              相关资源
              最近更新 更多