我有一个全局引用计数对象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
之间存在分歧,但是原始指针和唯一指针对于并发修改都是不安全的。实际上,reset
或 delete
将检查指针是否为空,然后删除并将其设置为空,这些步骤不是原子的。
fetch_add
和 fetch_sub
是 fetch 和 op 而不仅仅是 op 是有原因的:如果你不使用操作过程中观察到的值,它很可能是一个种族。
关于c++ - 将全局引用计数资源与原子同步——在这里放宽合适吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71129379/