c++ - 用于双重检查锁定的正确编译器内在函数?

标签 c++ multithreading locking intrinsics

在实现双重检查锁定时,在实现初始化双重检查锁定时,执行内存和/或编译器屏障的正确方法是什么?

像 std::call_once 这样的东西不是我想要的;太慢了。它通常只是在操作系统的 pthread_mutex_lock 和 EnterCriticalSection 之上实现。

在我的程序中,我经常遇到初始化情况,只要有一个线程设置最终指针,初始化就可以安全地重复。如果另一个线程比它设置指向单例对象的最终指针,它会删除它创建的内容并使用另一个线程的指针。我也经常在哪个线程“获胜”无关紧要的情况下使用它,因为它们都得出相同的结果。

这是一个不安全、过度设计的示例,使用 Visual C++ 内在函数:

MyClass *GetGlobalMyClass()
{
    static MyClass *const UNSET_POINTER = reinterpret_cast<MyClass *>(
        static_cast<intptr_t>(-1));

    static MyClass *volatile s_object = UNSET_POINTER;

    if (s_object == UNSET_POINTER)
    {
        MyClass *newObject = MyClass::Create();

        if (_InterlockedCompareExchangePointer(&s_object, newObject,
            UNSET_POINTER) != UNSET_POINTER)
        {
            // Another thread beat us.  If Create didn't return null, destroy.
            if (newObject)
            {
                newObject->Destroy();  // calls "delete this;", presumably
            }
        }
    }

    return s_object;
}

在弱有序内存架构上,我的理解是 s_object 的新值可能是在 MyClass::Create 内写入的其他变量之前对其他线程可见或MyClass::MyClass是可见的。此外,编译器本身可以在没有编译器屏障的情况下以这种方式安排代码(在 Visual C++ 中为 _WriteBarrier ,但 _InterlockedCompareExchange 充当屏障)。

我是否需要像商店栅栏那样的内在功能或其他东西来确保 MyClass的变量对 s_object 之前的所有线程都可见变得除了-1之外的东西?

最佳答案

幸运的是,C++ 中的规则非常简单:

If there is a data race, the behaviour is undefined.

在您的代码中,数据争用是由以下读取引起的,它与 __InterlockedCompareExchangePointer 中的写入操作冲突。

if (s_object.m_void == UNSET_POINTER)

无阻塞的线程安全解决方案可能如下所示。请注意,在 x86 上,与常规加载操作相比,具有顺序一致性的加载操作基本上没有任何开销。如果您关心其他架构,您还可以使用获取释放而不是顺序一致性

static std::atomic<MyClass*> s_object{nullptr};

MyClass* o = s_object.load(std::memory_order_seq_cst);
if (o == nullptr) {
    o = new MyClass{...};
    MyClass* expected = nullptr;
    if (!s_object.compare_exchange_strong(expected, o, std::memory_order_seq_cst)) {
        delete o;
        o = expected;
    }
}
return o;

关于c++ - 用于双重检查锁定的正确编译器内在函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24104140/

相关文章:

c - 带有互斥锁的 Pthreads 程序 - 每次都打印相同的字符串

python-3.x - 我应该如何设置 spaCy 服务器来处理多个并发请求(非阻塞)?

postgresql - SKIP LOCKED 除非所有行都被锁定

c++ - 用当前类专门化继承的模板函数

c++ - 你的测试应该有多详细/粒度?

android - Unity原生OpenGL贴图显示四次

c++ - 十六进制 std::string 的逆序

c++ - boost 互斥锁

C# Lock ("string") 不适用于动态参数?

c++ - 什么时候调用构造函数?