功能是线程安全的吗?

时间:2015-12-10 15:14:39

标签: c++ multithreading winapi thread-safety

我想避免多次从进程中的线程调用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; 
    }
}   

在这里输入代码

2 个答案:

答案 0 :(得分:0)

双重检查模式不是线程安全的,请查看本文http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

这是一个解决方案,类似于Qt的Q_GLOBAL_STATIC实现和chrome的符号实现。

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 :(得分:-1)

这是经典的双重检查锁定。这里的主要问题如下: 让我们说,第一个线程第一次进入函数。检查atom为0,锁定临界区并开始初始化。它将atom设置为非0,并且此更改将反映在主内存中(因为它可以!)。但是,RegisterClass调用的结果反映在主内存中,并保留在CPU缓存中。之后,CPU决定它过热并进入休眠状态。

在这个完美的时间,另一个线程在不同的CPU上进入该功能。它认为ATOM是非空的,并且很高兴地返回它 - 但无论谁想要使用原子都会让人大吃一惊!

修改

刚刚注意到有问题的编译器。由于VS2010不支持线程安全的静态变量,因此这里还存在另一个问题。原子可能会重新初始化为0秒。这可能发生在以下情形中:

线程一进入该功能,检查隐藏的&#39; inited&#39;与atom相关联的值,发现它为false,将atom设置为0,inited为true,进入临界区并注册class - 此时它将进入休眠状态。第二个线程进入,检查初始值,发现它是假的 - 因为它没有阅读围栏,所以它可以读取陈旧值 - 并将原子设置为0.需要我继续?

明显的解决方案(不是最好的解决方案,但考虑到约束条件)是在定义静态之前进入关键部分。