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

C 原子引用计数共享不可变数据是否需要内存屏障?,c,multithreading,atomic,memory-barriers,refcounting,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是否需要内存屏障?如果是,什么样的记

我有一些不可变的数据结构,我想使用引用计数进行管理,在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的原子内置。至于不变性,refcount是唯一一个在对象持续时间内发生变化的字段。

您是打算实现自己的
atomic\u dec
还是只是想知道系统提供的函数是否会按照您的要求运行


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

在x86上,它将变成一个带前缀的汇编指令,如
lock XADD

作为一条指令,它是不可中断的。作为一项新增的“功能,
锁定
前缀会产生一个完整的内存障碍:

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

实际上,在x86/x64和x86/x64上,内存屏障都是作为虚拟的
锁或
锁和
实现的,因为即使在保证可用的情况下,
mfence
在许多CPU上也会变慢,比如在64位模式下。(
因此,无论您喜欢与否,x86上都有一个完整的围栏作为额外的奖励。:-)

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


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

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

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

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

连贯缓存也不能节省您的时间。请查看内存重新排序的实际工作原理


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

我想知道是否有内存阻塞在这种情况下,IER是必要的,以及为什么需要+1“某物”来同步对refcount字段的访问。是否为“某物”"实际上是一个内存屏障,或另一个类似的缓存操作,需要遍历CPU规范和/或检查发出的代码。它不需要完全刷新缓存,也许CPU只会使使用的单个缓存线失效。编译器和CPU都必须确保指令不会在减量期间重新排序,但是基于减量结果的条件几乎保证了这一点。@Dietrich:在这种情况下,不会,因为后续操作是以减量结果为条件的,因此编译器不可能以有问题的方式重新排序如果ount为零,则只有一个线程可以访问所讨论的对象(即缺少bug)@Steve:我之所以提到它,是因为人们在讨论多线程的正确性时似乎过分担心缓存。像x86系统这样的现代多处理器将在硬件上解决所有问题。在一个缓存一致的系统中,如果你正在对内核或进行DMA传输的设备的驱动程序进行黑客攻击,你只需要担心缓存刷新s、 当然,这对性能很重要,但对正确性不重要。当然:你知道多核PowerPC是否一定有一致的缓存吗?但你是对的,原子是原子的,它是用显式缓存失效还是一致缓存实现的,或者其他什么,很少影响应用程序代码。有些事情你可以做uming相干缓存:您是否应该这样做是值得怀疑的