原子引用计数共享不可变数据是否需要内存屏障?

时间:2010-04-08 10:57:59

标签: c multithreading atomic memory-barriers refcounting

我有一些不可变的数据结构,我想使用引用计数来管理,在SMP系统上的线程之间共享它们。

这是发布代码的样子:

void avocado_release(struct avocado *p)
{
    if (atomic_dec(p->refcount) == 0) {
        free(p->pit);
        free(p->juicy_innards);
        free(p);
    }
}

atomic_dec是否需要内存屏障?如果是这样,会有什么样的记忆障碍?

附加说明:应用程序必须在PowerPC和x86上运行,因此欢迎任何特定于处理器的信息。我已经知道GCC原子内置。至于immutability,refcount是 only 字段,它在对象的持续时间内发生变化。

3 个答案:

答案 0 :(得分:11)

在x86上,它将变为锁定前缀汇编指令,如LOCK XADD 作为单一指令,它是不可中断的。作为一个增加的“feauture”,锁定前缀会导致完整的内存障碍:

  

“...锁定操作序列化所有未完成的加载和存储操作(即等待它们完成)。” ......“锁定操作相对于所有其他内存操作和所有外部可见事件都是原子操作。只有指令获取和页表访问才能传递锁定指令。锁定指令可用于同步由一个处理器写入的数据并由另一个处理器读取“。 - Intel® 64 and IA-32 Architectures Software Developer’s Manual,第8.1.2章。

事实上,在x86 / x64上,内存屏障在the .NETthe JAVA JIT中实现为虚拟LOCK ORLOCK AND。 无论你喜不喜欢,你都可以在x86上拥有一个完整的围栏作为额外的奖励。 :)

在PPC上,它是不同的。一个LL/SC对 - lwarx & stwcx - 内部有一个减法可用于将内存操作数加载到一个寄存器中,减去一个,然后在目标位置没有其他存储时将其写回,或者如果有的话,重试整个循环。 LL / SC可以被中断 它也不代表自动完整的围栏 然而,这并不以任何方式损害计数器的原子性 它只是意味着在x86的情况下,你也碰巧得到一个围栏,“免费” 在PPC上,可以通过发出(lw)sync instruction来插入完整的围栏。

总而言之,显式内存屏障不是原子计数器正常工作所必需的。

答案 1 :(得分:4)

区分原子访问(保证值的读取/修改/写入作为一个原子单元执行)和内存重新排序非常重要。

内存屏障阻止了读写的重新排序,重新排序与原子性完全正交。例如,在PowerPC上,如果您实现可能的最有效的原子增量,那么它将不会阻止重新排序。如果你想阻止重新排序,那么你需要一个lwsync或同步指令,或一些等效的高级(C ++ 11?)内存屏障。

声称“编译器无法以有问题的方式重新排序”的说法看起来像普通语句一样天真,因为编译器优化可能非常令人惊讶,因为CPU(特别是PowerPC / ARM / Alpha / MIPS)积极地重新排序内存操作

连贯的缓存也不能保存你。请参阅http://preshing.com/,了解内存重新排序的确如何运作。

然而,在这种情况下,我认为答案是不需要障碍。这是因为对于这种特定情况(引用计数),不需要引用计数与对象中的其他值之间的关系。一个例外是当引用计数达到零时。此时,确保来自其他线程的所有更新对当前线程都是可见的非常重要,因此可能需要读取获取障碍

答案 2 :(得分:2)

您打算实现自己的atomic_dec还是只是想知道系统提供的功能是否会按您的意愿运行?

作为一般规则,系统提供的原子递增/递减功能将应用所需的任何内存障碍来做正确的事情。您通常不必担心内存障碍,除非您正在做一些古怪的事情,比如实现您自己的无锁数据结构或STM库。