代码之家  ›  专栏  ›  技术社区  ›  kshahar

如何编写线程安全的自动测试

  •  7
  • kshahar  · 技术社区  · 16 年前

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

    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,不过我更喜欢一种至少跨平台的解决方案。

    6 回复  |  直到 11 年前
        1
  •  5
  •   tvanfosson    16 年前

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

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

    如果需要对另一个方法进行中间调用,您可能还需要小心确保同一个方法不会按顺序被调用两次。

        2
  •  9
  •   Jörg W Mittag    16 年前

    你可能想退房 CHESS: A Systematic Testing Tool for Concurrent Software 微软研究中心。它是多线程程序(包括.NET和本机代码)的测试框架。

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

        3
  •  3
  •   Ronny Brendel    11 年前

    Intel Threadchecker .

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

        4
  •  2
  •   Die in Sente    16 年前

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

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

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

        5
  •  0
  •   kshahar    16 年前

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

    //! 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
  •  0
  •   GregC Benjamin Baumann    13 年前

    拯救的恶作剧

    http://www.corensic.com/