[英]Is the function thread-safe?
我想避免從進程中的線程多次調用RegisterClassEx()。 為此,我修改了以下現有功能。
當前的代碼結構在使用替代方法來實現線程安全性方面存在一些限制,因此,現在,我試圖盡量減少更改。
請讓我知道您對以下代碼的想法。
foo()
{
static ATOM atom = 0;
if( atom == 0 )
{
{
EnterCriticalSection(&m_CSRegisterClassEx);
if( atom == 0 )
{
atom = RegisterClassEx(&tCls);
if( atom == 0)
{
ERROR(L"RegisterClassEx failed! );
LeaveCriticalSection(&m_CSRegisterClassEx);
return 0;
}
else
{
ERROR(L"RegisterClassEx good!");
LeaveCriticalSection(&m_CSRegisterClassEx);
return atom;
}
}
}
}
else
{
ERROR(L"using atom[%ld] from last call!", atom);
return atom;
}
}
在這里輸入代碼
雙重檢查模式不是線程安全的,請檢查本文http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
這是一個解決方案,類似於Qt的Q_GLOBAL_STATIC實現和chrome的signleton實現。
template<class T>
class TwStaticObject
{
public:
TwStaticObject(void)
: p(nullptr)
, x(0)
{
}
~TwStaticObject(void)
{
}
struct Deleter
{
Deleter(TwStaticObject& This)
: __this(This)
{
;
}
~Deleter()
{
if (__this.p)
{
delete __this.p;
__this.p = 0;
__this.x = 0;
}
}
TwStaticObject& __this;
};
static T* singletonInstance(TwStaticObject& thisobj)
{
if (InterlockedCompareExchange(&thisobj.x, 1, 0) == 0)
{
static T* obj = new T;
InterlockedExchangePointer((volatile PVOID *)&thisobj.p, (PVOID)obj);
static Deleter ThisDelter(thisobj);
}
else
{
while (thisobj.p == nullptr)
{
Sleep(0);
}
}
return thisobj.p;
}
T* volatile p ;
volatile long x ;
};
#define TwDefine_SingleTon(Type, FUN) \
static TwStaticObject<Type> ThisSingleTon##Type##FUN;\
static Type* FUN()\
{\
return TwStaticObject<Type>::singletonInstance(ThisSingleTon##Type##FUN);\
}
比注冊:
class Register{
public:
Register(){
atom = RegisterClassEx(&tCls);
}
ATOM atom;
};
TwDefine_Static(Register, _register);
如果需要,請調用_register()。
這是經典的雙重檢查鎖定。 這里的主要問題如下:假設線程1第一次進入函數。 檢查atom為0,鎖定關鍵部分並開始初始化。 它將原子設置為非0,並且此更改反映在主內存中(因為它可以!)。 但是,RegisterClass調用的結果不會反映在主內存中, 而是保留在CPU緩存中。 在那之后,CPU判斷為過熱並進入睡眠狀態。
在這個完美的時間,另一個線程在另一個CPU上進入函數。 它看到ATOM為非空,並愉快地返回它-但是誰將使用原子的人都將大吃一驚!
編輯
剛注意到編譯器有問題。 由於VS2010不支持線程安全的靜態變量,還有另外一個問題在這里。 原子有可能會被重新初始化為0秒。 在以下情況下可能會發生這種情況:
線程一進入函數,檢查與原子關聯的隱藏的“初始”值,發現其為false,將原子設置為0,初始化為true,進入臨界區並注冊類-此時它進入睡眠狀態。 第二個線程進入,檢查初始值,發現它是錯誤的-因為它的側面沒有讀取圍欄,因此它可以讀取過時的值-並將atom設置為0。我需要繼續嗎?
顯而易見的解決方案(不是最好的解決方案,但考慮到約束)是在定義靜態之前進入關鍵部分。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.