即使我已经使用 std::mutex 来保护访问,从技术上讲是否需要 std::atomic ?

Dyl*_*ier 3 c++ multithreading atomic thread-safety

从技术上来说,似乎需要std::atomic根据标准保护多个线程读取/写入的所有数据,但对于复杂数据初始化的情况来说,这似乎很奇怪。对于 C++ 内存模型,以下未定义行为或使用锁是否可以解决问题?

class Foo {
public:
    int x;
    int y;

    static Foo* GetFoo();

private:
    static std::mutex sm_lock;
    static Foo* sm_foo;
};

std::mutex Foo::sm_lock;
Foo* sm_foo = nullptr;

Foo* Foo::GetFoo() {
    std::lock_guard<std::mutex> guard(sm_lock);
    if (!sm_foo) {
        sm_foo = new Foo();
        if (sm_foo) {
            sm_foo->x = 42;
            sm_foo->y = 11;
        }
    }

    return sm_foo;
}
Run Code Online (Sandbox Code Playgroud)

我知道在实践中这几乎总是适用于 x86_64,但是内存排序较弱的平台又如何呢?

Ted*_*gmo 6

即使我已经使用 std::mutex 来保护访问,从技术上讲是否需要 std::atomic ?

不,互斥锁使其安全 - 但您不需要sm_foo在 后检查是否为非空newnew(如您的示例中所使用的)如果失败将引发异常:

Foo* Foo::GetFoo() {
    std::lock_guard<std::mutex> guard(sm_lock);
    if (!sm_foo) {
        sm_foo = new Foo();    
        sm_foo->x = 42;      // sm_foo can't be a null pointer here
        sm_foo->y = 11;
    }
    return sm_foo;
}
Run Code Online (Sandbox Code Playgroud)

更好的方法是根本不使用指针。这不需要互斥锁,因为实例化是线程安全的(因为该语言中引入了线程):

class Foo {
public:
    int x;
    int y;

    static Foo& GetFoo();

private:
    // if it's supposed to be a singleton:
    Foo(int X, int Y) : x(X), y(Y) {}

    Foo(const Foo&) = delete;
    Foo(Foo&&) = delete;
    Foo& operator=(const Foo&) = delete;
    Foo operator=(Foo&&) = delete;
    ~Foo() = default;
};

Foo& Foo::GetFoo() {
    static Foo sm_foo{42,11}; // thread safe instantiation
    return sm_foo;
}
Run Code Online (Sandbox Code Playgroud)

如果您确实GetFoo()返回一个指针,那没问题(尽管更常见的是返回对实例的引用):

class Foo {
    // ...
    static Foo* GetFoo();
};

Foo* Foo::GetFoo() {
    static Foo sm_foo{42,11}; // still a thread safe instantiation
    return &sm_foo;
}
Run Code Online (Sandbox Code Playgroud)

  • @DyllonGagnier 如果您实际上并不是在谈论 C++ 线程之间的数据竞争,那么答案不需要相同。如果您谈论的是完全不同的线程/内存概念,那么它与 C++ 线程和内存模型保证没有任何关系。 (4认同)