[英]How to avoid race condition when refcounting without a mutex?
I'm trying to figure out how to avoid the race condition in the following code, with thread A acquiring the datablock, then thread B Releasing/deleting it, and then thread A AddRefing it. 我试图弄清楚如何避免以下代码中的竞争条件,线程A获取数据块,然后线程B释放/删除它,然后线程A AddRefing它。 Is it possible to fix this without a mutex?
没有互斥锁可以解决这个问题吗? I think that it's possible to fix this with atomic_thread_fence, but I really have no idea how it would apply to this situation.
我认为可以用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();
}
};
I think that it's possible to fix this with atomic_thread_fence
我认为可以用atomic_thread_fence来解决这个问题
atomic_thread_fence
is only useful if you're using memory orderings weaker than the default seq_cst
(see Jeff Preshing's article about C++11 fences for more about fences and memory ordering. Jeff Preshing's articles are excellent; definitely read most of those while you're trying to grok lockless programming). atomic_thread_fence
仅在您使用比默认seq_cst
弱的内存排序时才有用(请参阅Jeff Preshing关于C ++ 11围栏的文章,了解有关围栏和内存排序的更多信息.Jeff Preshing的文章非常出色;绝对是您阅读的大部分内容试图解锁无锁编程)。
atomic_thread_fence
can only limit reordering of how the current thread's memory operations become globally visible. atomic_thread_fence
只能限制当前线程的内存操作如何全局可见的重新排序。 It doesn't itself wait for something in other threads. 它本身并不等待其他线程中的某些东西。
When you try to add a reference, be prepared to find that it had already dropped to zero . 当您尝试添加引用时,请准备好发现它已经降为零 。 ie
AddRef()
can fail, if you were too late and another thread has already started to destroy the refcounted object. 即
AddRef()
可能会失败,如果你太晚了,另一个线程已经开始销毁refcounted对象。
So the implementation of AddRef would do something like 所以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;
}
We're using a CAS loop as a conditional fetch_add
instead of doing a fetch_add
and then un doing it if the old value was too low. 我们使用CAS环路有条件
fetch_add
而不是做一个fetch_add
,然后取消做,如果旧值太低。 The latter would require extra work to avoid a race condition if two threads were incrementing at once. 如果两个线程一次递增,后者将需要额外的工作来避免竞争条件。 (The 2nd thread would see and old_count of 1 and think it was ok.) You could maybe work around that by having the
Release
function set the refcount to a large negative number before starting to destroy a block, but this is easy to verify and a CAS that almost always succeeds on the first try is barely any slower than an actual fetch_add
. (第二个线程会看到和old_count为1并且认为它没问题。)你可以通过让
Release
函数在开始销毁块之前将refcount设置为一个大的负数来解决这个问题,但这很容易验证,在第一次尝试时几乎总是成功的CAS几乎不比实际的fetch_add
慢。 The separate atomic load is nearly free compared to CAS, especially on x86. 与CAS相比,单独的原子负载几乎是免费的,特别是在x86上。 (You could use
memory_order_relaxed
for it to make it near-free on weakly-ordered architectures too.) (您可以使用
memory_order_relaxed
使其在弱有序的体系结构上几乎免费。)
Note that your refcount can't be part of the datablock that you delete
when the refcount reaches zero . 请注意,当refcount达到零时,您的引用计数不能是您
delete
的数据块的一部分 。 If you did that, a thread that called get_datablock
and did m_datablock.load()
, then slept, then dereferenced that pointer with datablock->AddRef()
could segfault (or cause other undefined behaviour) if the pointed-to memory was deleted by another thread while it was asleep. 如果你这样做,一个调用
get_datablock
并执行m_datablock.load()
的线程然后睡眠,然后取消引用带有datablock->AddRef()
指针可能会段错误(或导致其他未定义的行为)如果指向的内存被删除了在睡着的时候另一个线程。
This answer is doesn't solve the whole problem (of managing refcount blocks while still allowing an exchange
in your set_datablock
API. I'm not sure that API design really works. 这个答案并没有解决整个问题(管理refcount块,同时仍允许在
set_datablock
API中进行exchange
。我不确定API设计是否真的有效。
It's also not a complete working atomic_shared_pointer
implementation. 它也不是一个完整的
atomic_shared_pointer
实现。
If you want to know how that works, look at its documents, or hopefully someone's written a post about how it's implemented. 如果您想知道它是如何工作的,请查看其文档,或希望有人写一篇关于它如何实现的文章。 Open-source library implementations of it exist, but are probably pretty hard to read.
它的开源库实现存在,但可能很难阅读。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.