15

我有一些不可变的数据结构,我想使用引用计数来管理它们,在 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 atomic builtins。至于不变性,引用计数是唯一在对象的持续时间内发生变化的字段。

4

3 回答 3

16

在 x86 上,它会变成一个带lock前缀的汇编指令,比如LOCK XADD.
作为一条指令,它是不可中断的。作为一个附加的“功能”,lock前缀会导致完整的内存屏障:

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

内存屏障实际上是​​作为虚拟实现的,LOCK OR或者LOCK AND在x86/x64 上的 .NETJAVA JITmfence中实现,因为在许多 CPU 上速度较慢,即使它保证可用,例如在 64 位模式下。(lock xchg 是否与 mfence 具有相同的行为?
因此,无论您喜欢与否,您都可以在 x86 上使用完整的栅栏作为额外的奖励。:-)

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

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

于 2010-04-08T20:55:51.857 回答
9

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

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

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

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

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

于 2015-02-17T22:13:29.640 回答
2

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

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

于 2010-04-08T11:02:05.910 回答