当非原子时,C++成员更新临界区内的可见性

Wat*_*ter 5 c++ multithreading atomic

我偶然发现了以下Code Review StackExchange并决定阅读它以供练习.在代码中,有以下内容:

注意:我不是在寻找代码审查,这只是链接代码的复制粘贴,因此您可以专注于手头的问题而不会干扰其他代码.我对实现'智能指针'不感兴趣,只是了解内存模型:

// Copied from the link provided (all inside a class)

unsigned int count;
mutex m_Mutx;

void deref()
{
    m_Mutx.lock();
    count--;
    m_Mutx.unlock();
    if (count == 0)
    {
        delete rawObj;
        count = 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

看到这一点让我立刻想到"如果两个线程何时进入count == 1并且两者都没有看到彼此的更新怎么办?最终可以看到count为零和双重删除?并且两个线程可能导致count变为-1然后删除从不发生?

互斥锁将确保一个线程进入临界区,但这是否保证所有线程都会正确更新?C++内存模型告诉我什么,所以我可以说这是一个竞争条件?

我查看了内存模型cppreference页面std :: memory_order cppreference,但后一页似乎处理了一个原子参数.我找不到我想要的答案,或者我误解了它.任何人都可以告诉我,我所说的是错误还是正确,以及这段代码是否安全?

如果代码坏了则更正代码:

这是否正确的答案将计数变成原子成员?或者这是否有效并且在释放互斥锁上的锁之后,所有线程都看到了值?

如果这被认为是正确的答案,我也很好奇:

注意:我不是在寻找代码审查并试图查看这种解决方案是否能解决与C++内存模型相关的问题.

#include <atomic>
#include <mutex>

struct ClassNameHere {
    int* rawObj;
    std::atomic<unsigned int> count;
    std::mutex mutex;

    // ...

    void deref()
    {
        std::scoped_lock lock{mutex};
        count--;
        if (count == 0)
            delete rawObj;
    }
};
Run Code Online (Sandbox Code Playgroud)

Arn*_*gel 1

如果两个线程可能*deref()同时进入,那么,无论 之前或之前的预期值如何count,都会发生数据争用,并且您的整个程序,甚至您希望按时间顺序优先的部分,都会具有C++中所述的未定义行为[intro.multithread/20] (N4659) 中的标准

两个动作可能是并发的,如果

(20.1) 它们由不同的线程执行,或者

(20.2) 它们是无序的,至少有一个是由信号处理程序执行的,并且它们不是都由同一个信号处理程序调用执行。

如果程序的执行包含两个潜在并发冲突的操作,并且至少其中一个操作不是原子操作,并且两者都发生在另一个操作之前,则该程序的执行将包含数据争用,除了下面描述的信号处理程序的特殊情况之外。任何此类数据竞争都会导致未定义的行为。

当然,在这种情况下,潜在的并发操作是锁定部分外部的读取和其中count的写入。count

*) 也就是说,如果当前输入允许的话。

更新 1:您引用的描述原子内存顺序的部分解释了原子操作如何相互同步以及如何与其他同步原语(例如互斥体和内存屏障)同步。换句话说,它描述了如何使用原子进行同步,以便某些操作不会出现数据争用。它不适用于此处。该标准在这里采取保守的方法:除非标准的其他部分明确表明两个冲突的访问不是并发的,否则就会出现数据竞争,因此会出现 UB(其中冲突意味着相同的内存位置,并且至少其中一个不是) t 只读)。