Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu,《C++ 编码标准

PrevUpHomeNext

第 19 章。Boost.Lockfree

Tim Blechmann

根据 Boost 软件许可协议 1.0 版分发。(请参阅随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制副本)

目录

简介与动机
示例
理由
内存管理
ABA 预防
进程间支持
参考
头文件 <boost/lockfree/policies.hpp>
头文件 <boost/lockfree/queue.hpp>
头文件 <boost/lockfree/spsc_queue.hpp>
头文件 <boost/lockfree/spsc_value.hpp>
头文件 <boost/lockfree/stack.hpp>
附录
支持的平台与编译器
参考文献

简介与术语

术语 非阻塞 指的是并发数据结构,它们不使用传统的同步原语(如锁)来确保线程安全。Maurice Herlihy 和 Nir Shavit(比较 《多处理器编程的艺术》)区分了 3 种类型的非阻塞数据结构,每种结构都具有不同的属性

  • 如果每个并发操作都保证在有限的步骤内完成,则数据结构是 无等待 的。因此,可以给出操作次数的最坏情况保证。
  • 如果某些并发操作保证在有限的步骤内完成,则数据结构是 无锁 的。虽然理论上某些操作可能永远不会取得任何进展,但在实际应用中这种情况不太可能发生。
  • 如果并发操作保证在有限的步骤内完成,除非另一个并发操作干扰,则数据结构是 无障碍 的。

某些数据结构只有在特定限制下使用时才能以无锁方式实现。《boost.lockfree》实现的相关方面是生产者和消费者线程的数量。单生产者 (sp) 或 多生产者 (mp) 表示只允许单个线程或多个并发线程向数据结构添加数据。单消费者 (sc) 或 多消费者 (mc) 表示从数据结构中移除数据的等效情况。

非阻塞数据结构的属性

非阻塞数据结构不依赖于锁和互斥锁来确保线程安全。同步完全在用户空间中完成,无需与操作系统直接交互 [7]。这意味着它们不易受到优先级反转等问题的影响(低优先级线程需要等待高优先级线程)。

非阻塞数据结构不依赖于锁,而是需要 原子操作(在没有中断的情况下执行的特定 CPU 指令)。这意味着任何线程要么看到操作之前的状态,要么看到操作之后的状态,但无法观察到中间状态。并非所有硬件都支持相同的原子指令集。如果硬件中没有原子指令,则可以使用锁在软件中模拟。然而,这具有明显的缺点,即失去无锁属性。

非阻塞数据结构的性能

在讨论非阻塞数据结构的性能时,必须区分 均摊 成本和 最坏情况 成本。“无锁”和“无等待”的定义仅提及操作的上限。因此,无锁数据结构不一定是每个用例的最佳选择。为了最大化应用程序的吞吐量,应该考虑高性能并发数据结构 [8]

为了优化系统的延迟或避免优先级反转(这在实时应用程序中可能是必要的),无锁数据结构将是更好的选择。一般来说,我们建议考虑是否需要无锁数据结构,或者并发数据结构是否足够。在任何情况下,我们都建议针对特定的工作负载对不同的数据结构进行基准测试。

阻塞行为的来源

除了锁和互斥锁(我们在《boost.lockfree》中无论如何都不使用它们)之外,还有三个其他方面可能会违反无锁性

原子操作

某些架构本身并不在硬件中提供必要的原子操作。如果不是这种情况,则使用自旋锁在软件中模拟它们,而自旋锁本身是阻塞的。

内存分配

从操作系统分配内存不是无锁的。这使得实现真正的动态大小的非阻塞数据结构成为不可能。《boost.lockfree》的基于节点的数据结构使用内存池来分配内部节点。如果此内存池耗尽,则必须从操作系统分配新节点的内存。但是,《boost.lockfree》的所有数据结构都可以配置为避免内存分配(相反,特定的调用将失败)。这对于需要无锁内存分配的实时系统尤其有用。

异常处理

C++ 异常处理不对其实时行为做出任何保证。因此,我们不鼓励在无锁代码中使用异常和异常处理。

数据结构

boost.lockfree 实现了四种无锁数据结构

boost::lockfree::queue

无锁多生产者/多消费者队列

boost::lockfree::stack

无锁多生产者/多消费者堆栈

boost::lockfree::spsc_queue

无等待单生产者/单消费者队列(通常称为环形缓冲区)

boost::lockfree::spsc_value

无等待单生产者/单消费者值(通常称为三重缓冲区)

数据结构配置

数据结构可以使用 Boost.Parameter 风格的模板进行配置

boost::lockfree::fixed_sized

将数据结构配置为 固定大小。内部节点存储在数组内部,并通过数组索引寻址。这限制了队列的可能大小为索引类型可以寻址的元素数量(通常为 2**16-2),但在缺少双倍宽度比较和交换指令的平台上,这是实现无锁性的最佳方法。

boost::lockfree::capacity

在编译时设置数据结构的 容量。这意味着数据结构是固定大小的。

boost::lockfree::allocator

定义分配器。

boost::lockfree::allow_multiple_reads

配置 boost::lockfree::spsc_value 以允许多次读取内容。



[7] 自旋锁也不直接与操作系统交互。但是,拥有线程可能被操作系统抢占,这违反了无锁属性。

[8] Intel 的线程构建模块库 提供了许多高效的并发数据结构,这些数据结构不一定是无锁的。


PrevUpHomeNext