簡體   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