内存模型

多线程的基础是定义良好的内存模型。对内存有基本的了解,有助于更深入地了解多线程的挑战。

不要使用volatile进行同步

C++与C#或Java相比,volatile关键字没有多线程语义。在C#或Java中,volatile声明了一个原子变量,如std::atomic在C++中声明了一个原子一样,通常用于可以进行更改的对象。由于这一特性,没有优化的存储会发生在缓存中。

不要让程序无锁

这个建议听起来很荒谬,但是这个建议的理由很简单,无锁编程非常容易出错,并且需要在这个领域是专家级别的人,才能保证很少出错。如果需要实现无锁的数据结构,请务必注意ABA问题。

如果使用无锁程序,请使用成熟的模式

如果已经确定要使用无锁方案,那么请使用成熟的模式。

  1. 简单的共享原子布尔值或原子计数器。
  2. 使用线程安全,甚至无锁的容器来支持消费者/生产者的场景。如果使用的容器是线程安全的,则可以将值放入容器中或从容器中取出,而不必担心同步的问题。这就将应用程序的挑战转移到基础设施中。

不要构建自定义的抽象方式,尽量使用当前语言能够保证的方式

共享变量的线程安全初始化,可以通过多种方式完成。可以依赖于C++运行时的保证,比如:常量表达式、带有块作用域的静态变量,或者使用函数std::call_oncestd::once_flag组合使用。这里用C++编程,即使使用非常复杂的获取-发布语义,也可以构建基于原子的抽象。一开始最好不要这样做,除非不得已。这意味着,通过度量关键代码的性能来确定瓶颈时,只有当明确自定义版本比当前语言默认的方式性能更好时,再进行更改。

不要重新发明轮子

编写线程安全的数据结构是一项颇具挑战性的工作,这要比编写无锁的数据结构更困难。因此,最好使用现成的库,如Boost.LockfreeCDS.

Boost.Lockfree

Boost.Lockfree支持三种不同的数据结构:

Queue:无锁的多生产/多消费者队列

Stack:无锁的多产品/多消费者堆栈

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

CDS

CDS代表并发数据结构,包含许多侵入式(非拥有)和非侵入式(拥有)容器。因为它们会自动管理元素,所以标准模板库的容器是非侵入的。

  • 堆栈(无锁)
  • 队列和带优先级的队列 (无锁)
  • 有序列表
  • 有序的set和map(无锁和有锁)
  • 无序的set和map(无锁和有锁 )