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

Die*_*Epp 12 c multithreading atomic refcounting memory-barriers

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

这是发布代码的样子:

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

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

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

And*_*ass 11

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

"...锁定操作序列化所有未完成的加载和存储操作(即等待它们完成)." ......"锁定操作对于所有其他内存操作和所有外部可见事件都是原子操作.只有指令获取和页表访问才能传递锁定指令.锁定指令可用于同步由一个处理器写入的数据并由另一个处理器读取".- 英特尔®64和IA-32架构软件开发人员手册,第8.1.2章.

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

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

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


Bru*_*son 7

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

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

声称“编译器不可能以有问题的方式重新排序”作为一般性陈述似乎很幼稚,因为编译器优化可能非常令人惊讶,并且因为 CPU(特别是 PowerPC/ARM/Alpha/MIPS)积极地重新排序内存操作。

连贯的缓存也不会拯救你。请参阅https://preshing.com/archives/以了解内存重新排序的实际工作原理。

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