我有一个关于使用无锁队列的问题。
假设我有一个单生产者单消费者队列,其中生产者和消费者绑定到不同的核心。队列元素是共享内存的缓冲区,由生产者和消费者在开始时进行映射。
生产者获取一个队列元素,用数据填充缓冲区并将其入队,消费者将元素出列,读取并以某种方式处理它。
作为无锁队列的用户,我是否必须明确确保生产者写入的缓冲区对用户可见?还是算法核心的 CAS(或其他类似)原语会自动提供障碍?
我见过的几个示例使用整数作为有效负载,因此不会出现内存同步的问题。
谢谢,
我有一个关于使用无锁队列的问题。
假设我有一个单生产者单消费者队列,其中生产者和消费者绑定到不同的核心。队列元素是共享内存的缓冲区,由生产者和消费者在开始时进行映射。
生产者获取一个队列元素,用数据填充缓冲区并将其入队,消费者将元素出列,读取并以某种方式处理它。
作为无锁队列的用户,我是否必须明确确保生产者写入的缓冲区对用户可见?还是算法核心的 CAS(或其他类似)原语会自动提供障碍?
我见过的几个示例使用整数作为有效负载,因此不会出现内存同步的问题。
谢谢,
诸如比较和交换之类的互锁原语通常以具有不同内存屏障语义的变体形式出现。它们在体系结构之间有所不同,但通常您希望(最迟)在生产者中的操作上具有“释放”语义,使结构对消费者可见,并且在您之前在消费者中具有“获取”语义访问数据结构。
在某些架构(特别是 vanilla x86)上,您实际上并没有选择,因为每个互锁操作都意味着一个完整的障碍——但如果这让您养成不请求任何障碍的习惯,由于墨菲,它会回来咬人你在其他一些架构上。
(相反,也由于 Murphy,如果您仔细研究选项并在各处插入正确的障碍,事件可能会合谋使您编写的任何代码都不需要在 x86 之外的任何东西上运行)。
根据定义,这是特定于架构的。对于 Intel CPU 上的 GCC,使用GCC Atomic Builins - 其中大多数都意味着完整的内存屏障。
一些架构将内存屏障绑定到 CAS;x86/x64 就是其中之一。
其他人(例如 ARM)没有。在 ARM 上,您执行 LL/SC,前后手动设置纯数据内存屏障。
作为无锁队列的用户,我是否必须明确确保生产者写入的缓冲区对用户可见?还是算法核心的 CAS(或其他类似)原语会自动提供障碍?
从语义上讲,将数据推送到并发队列应该至少有一个释放栅栏,从队列中弹出应该至少有一个获取栅栏。我相信一个好的无锁实现不应该把对内存栅栏等的关心强加给它的用户。即使无锁算法不会自动将栅栏放置在适当的位置(或者不是针对每个架构),实现,而不是用户,也必须确保正确的可见性和顺序。用户应该只关心选择“正常工作”的实现(可能/必须包括测试它是否真的有效):)