在没有互斥锁的情况下重新计数时如何避免竞争条件?
How to avoid race condition when refcounting without a mutex?
我正在尝试弄清楚如何避免以下代码中的竞争条件,线程 A 获取数据块,然后线程 B 释放/删除它,然后线程 A AddRefing 它。 是否可以在没有互斥锁的情况下解决此问题?我认为可以用atomic_thread_fence解决这个问题,但我真的不知道它如何适用于这种情况。
#include <atomic>
class Foo
{
std::atomic<Datablock*> datablock
public:
Datablock * get_datablock()
{
Datablock * datablock = m_datablock.load();
if(datablock) datablock->AddRef();
return datablock;
}
void set_datablock(Datablock* datablock)
{
datablock = m_datablock.exchange(datablock);
if(datablock) datablock->Release();
}
};
atomic_thread_fence来解决这个问题
仅当您使用的内存排序弱于默认seq_cst
时,atomic_thread_fence
才有用(有关围栏和内存排序的更多信息,请参阅 Jeff Preshing 关于 C++11 围栏的文章。杰夫·普雷辛的文章非常出色;当您尝试尝试无锁编程时,一定要阅读其中的大部分)。
atomic_thread_fence
只能限制当前线程的内存操作如何全局可见的重新排序。 它本身不会等待其他线程中的某些内容。
当您尝试添加引用时,请准备好发现它已降至零。 即AddRef()
可能会失败,如果你为时已晚,另一个线程已经开始销毁 refcounted 对象。
所以AddRef的实现会做类似的事情
bool AddRef() {
int old_count = m_refcount;
do {
if (old_count <= 0) {
// we were too late; refcount had already dropped to zero
// so another thread is already destroying the data block
return false;
}
}while( !m_refcount.compare_exchange_weak(old_count, old_count+1) );
return true;
}
我们使用 CAS 循环作为条件fetch_add
,而不是执行fetch_add
,然后在旧值太低时取消执行。 后者需要额外的工作来避免两个线程同时递增时的争用情况。 (第二个线程会看到并old_count 1 并认为没问题。 您可以通过让Release
函数在开始销毁块之前将 refcount 设置为大的负数来解决这个问题,但这很容易验证,并且几乎总是在第一次尝试时成功的 CAS 几乎不比实际fetch_add
慢。 与 CAS 相比,单独的原子负载几乎是免费的,尤其是在 x86 上。 (您也可以使用memory_order_relaxed
使其在弱序架构上接近自由。
请注意,您的引用计数不能是引用计数达到零时delete
的数据块的一部分。 如果这样做,则调用get_datablock
并执行m_datablock.load()
的线程,然后休眠,然后取消引用该datablock->AddRef()
指针,如果指向的内存在休眠时被另一个线程删除,则可能会出现段错误(或导致其他未定义的行为)。
这个答案并不能解决整个问题(在管理 refcount 块的同时仍然允许在set_datablock
API 中进行exchange
。 我不确定 API 设计是否真的有效。
它也不是一个完整的工作atomic_shared_pointer
实现。
如果你想知道它是如何工作的,看看它的文档,或者希望有人写了一篇关于它是如何实现的帖子。 它的开源库实现是存在的,但可能很难阅读。
- 大量序列中核苷酸类型的快速计数
- 使用新行和不使用新行读取文件
- 引用 std::shared:ptr 以避免引用计数
- 设计模式,以避免不必要地添加抽象函数以适应新功能
- 引用计数智能指针如何避免或处理引用计数器溢出?
- 在没有互斥锁的情况下重新计数时如何避免竞争条件?
- 英特尔®事务同步扩展新指令 (TSX-NI) 与英特尔 TSX 有何不同?
- 如何避免构建新字符串
- 将新对象shared_pt分配给 A[1],则 A[1] 中包含的原始对象的引用计数减少.如何
- 在避免新分配的同时,const变量的复杂初始化
- 避免每次要引入新功能时都使用新类
- 在将新创建的对象传递给函数时是否可以避免复制构造函数
- 视觉计数重复,并将唯一条目放入数组 [C++] 中的新数组中
- 避免在这种情况下使用"新"
- C++:是否必须在包装器中放入一个策略以避免裸新/删除
- 如果新对象是一个参数并且可以修改,请避免构造它
- 新的编译器-使用计数
- 避免在c++中创建新的不可变实例的样板文件
- 重定向计数到用winapi创建的新缓冲区
- 为什么要根据ISO C++避免在新的之后检查null