[英]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.