旋转锁总是需要内存屏障吗?在记忆障碍上旋转昂贵吗?

bla*_*ais 9 lock-free spinlock memory-barriers barrier

在大多数情况下,我写了一些无锁代码,可以在本地读取时正常工作.

本地旋转读取内存是否必然意味着我必须在旋转读取之前始终插入内存屏障?

(为了验证这一点,我设法生成一个读写器组合,导致读者永远不会看到写入的值,在某些非常特定的条件下 - 专用CPU,连接到CPU的进程,优化器一直向上,没有其他工作在循环中完成 - 所以箭头指向那个方向,但我不完全确定旋转内存屏障的成本.)

如果在缓存的存储缓冲区中没有任何内容被刷新,那么旋转内存屏障的成本是多少?即,所有过程都在做(在C中)

while ( 1 ) {
    __sync_synchronize();
    v = value;
    if ( v != 0 ) {
        ... something ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我是否正确地假设它是免费的并且它不会阻碍任何流量的内存总线?

另一种方法是问:内存屏障是否会执行任何操作:刷新存储缓冲区,对其应用失效,并阻止编译器在其位置重新排序读/写?


反汇编,__ sync_synchronize()似乎转化为:

lock orl
Run Code Online (Sandbox Code Playgroud)

从英特尔手册(同样模糊的新手):

Volume 3A: System Programming Guide, Part 1 --   8.1.2

Bus Locking

Intel 64 and IA-32 processors provide a LOCK# signal that
is asserted automatically during certain critical memory
operations to lock the system bus or equivalent link.
While this output signal is asserted, requests from other
processors or bus agents for control of the bus are
blocked.

[...]

For the P6 and more recent processor families, if the
memory area being accessed is cached internally in the
processor, the LOCK# signal is generally not asserted;
instead, locking is only applied to the processor’s caches
(see Section 8.1.4, “Effects of a LOCK Operation on
Internal Processor Caches”).
Run Code Online (Sandbox Code Playgroud)

我的翻译:"当你说LOCK时,这将是昂贵的,但我们只在必要时才这样做."


@BlankXavier:

我测试过如果编写器没有明确地从存储缓冲区中推出写入并且它是在该CPU上运行的唯一进程,则读者可能永远不会看到编写器的效果(我可以使用测试程序重现它,但是正如我上面提到的,它只发生在特定的测试中,具有特定的编译选项和专用的核心分配 - 我的算法运行正常,只有当我对它的工作方式感到好奇并编写了我认识到它可能具有的显式测试时未来的问题).

我认为默认情况下,简单写入是WB写入(回写),这意味着它们不会立即刷新,但读取将采用其最新值(我认为它们称之为"存储转发").所以我为作者使用CAS指令.我在英特尔手册中发现了所有这些不同类型的写入实现(UC,WC,WT,WB,WP),英特尔第3卷第11-10章,仍在了解它们.

我的不确定性在读者方面:我从McKenney的论文中了解到,还有一个失效队列,一个从总线进入缓存的传入失效队列.我不确定这部分是如何工作的.特别是,你似乎暗示循环通过正常的读(即非LOCK'ed,没有障碍,只使用挥发性,以确保优化离开读取一次编译)将检查进入"无效队列"每次(如果存在这样的事情).如果一个简单的读是不够的(即可以读取它似乎仍然有效的旧高速缓存行排队无效挂起(这听起来有点语无伦次地我也一样,但如何失效的队列工作呢?)),那么原子读会是必要的,我的问题是:在这种情况下,这会对公交车产生什么影响吗?(我想可能不是.)

我还在阅读英特尔手册,虽然我看到了关于商店转发的很好的讨论,但我还没有找到关于无效队列的好讨论.我决定将我的C代码转换为ASM并进行实验,我认为这是真正了解其工作原理的最佳方式.

Olo*_*ell 5

“xchg reg,[mem]”指令将通过内核的 LOCK 引脚发出其锁定意图的信号。该信号穿过其他内核并缓存到总线主控总线(PCI 变体等),总线主控总线将完成它们正在做的事情,最终 LOCKA(确认)引脚将向 CPU 发出 xchg 可能完成的信号。然后LOCK信号被关闭。此序列可能需要很长时间(数百个 CPU 周期或更多)才能完成。之后,其他核心的相应高速缓存线将失效,并且您将拥有一个已知状态,即已在核心之间同步的状态。

xchg 指令是实现原子锁所需的全部指令。如果锁定本身成功,您就可以访问已定义锁定以控制访问的资源。这样的资源可以是内存区域、文件、设备、函数或其他任何东西。尽管如此,程序员始终需要编写代码,在该资源被锁定时使用该资源,而在未锁定时则不使用该资源。通常,成功锁定后的代码序列应尽可能短,以便尽可能少地阻碍其他代码获取对资源的访问权限。

请记住,如果锁定不成功,您需要通过发出新的 xchg 重试。

“无锁”是一个有吸引力的概念,但它需要消除共享资源。如果您的应用程序有两个或多个内核同时读取和写入公共内存地址,则“无锁”不是一个选项。


小智 2

我可能没有正确理解这个问题,但是......

如果你正在旋转,一个问题是编译器优化你的旋转。挥发性解决了这个问题。

内存屏障(如果有的话)将由写入者而不是读取者向自旋锁发出。作者实际上不必使用它 - 这样做可以确保写入立即被推出,但无论如何它很快就会消失。

该屏障防止执行该代码的线程在其位置上重新排序,这是其其他成本。