c++ - 将全局引用计数资源与原子同步——在这里放宽合适吗?

标签 c++ multithreading c++11 atomic stdatomic

我有一个全局引用计数对象obj,我想通过使用原子操作来防止数据竞争:

T* obj;                  // initially nullptr
std::atomic<int> count;  // initially zero

我的理解是,我需要在写入obj 后使用std::memory_order_release,以便其他线程知道它正在创建:

void increment()
{
    if (count.load(std::memory_order_relaxed) == 0)
        obj = std::make_unique<T>();

    count.fetch_add(1, std::memory_order_release);
}

同样,我需要在读取计数器时使用std::memory_order_acquire,以确保线程可以看到正在更改的obj:

void decrement()
{
    count.fetch_sub(1, std::memory_order_relaxed);

    if (count.load(std::memory_order_acquire) == 0)
        obj.reset();
}

我不相信上面的代码是正确的,但我也不完全确定为什么。我觉得在 obj.reset() 被调用之后,应该有一个 std::memory_order_release 操作来通知其他线程。对吗?

是否还有其他可能出错的地方,或者我对这种情况下的原子操作的理解完全错误?

最佳答案

不管内存顺序如何都是错误的。

正如@MaartenBamelis 指出的increment 的并发调用,对象被构建了两次。并发 decrement 也是如此:对象被重置两次(这可能导致两次析构函数调用)。

请注意,在 T* obj; 声明和将其用作 unique_ptr 之间存在分歧,但是原始指针和唯一指针对于并发修改都是不安全的。实际上,resetdelete 将检查指针是否为空,然后删除并将其设置为空,这些步骤不是原子的。

fetch_addfetch_sub 是 fetch 和 op 而不仅仅是 op 是有原因的:如果你不使用操作过程中观察到的值,它很可能是一个种族。

关于c++ - 将全局引用计数资源与原子同步——在这里放宽合适吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71129379/

相关文章:

c++ - 是否可以从 std::abort 中恢复?

c++ - 双向链表按顺序插入

c++ - 无法在 C++ 中解析符号

c++ - 如何让 CppUnit 轻量级运行?

.net - 加强托管线程和操作系统线程之间的关系(CUDA 用例)

java - 在 Android 中持续更新 UI 线程的最有效方法

c++ - 带线程的运算符++(前缀)

c++ - 在 C++11 中将 fstream 引用作为函数参数传递

Qt 需要 C++11 支持

C++ - 恢复模板或转换为模板