书库技术与未来What every systems programmer should know about concurrency
书籍封面

What every systems programmer should know about concurrency

作者 Matt Kline
15.0 分钟

摘要

系统程序员应该知道的并发知识

  • 本文旨在帮助系统程序员理解并发编程的基础,包括原子性、内存排序、以及如何在无锁环境下编写并发代码。
  • 你能获得:理解原子操作原理、掌握内存排序优化技巧、避免常见并发陷阱,提升系统性能。

核心内容:

1. 原子性是并发编程的基础:

  • 原子性指的是一个操作不可再分,要么全部执行,要么完全不执行。在多线程环境下,非原子操作可能导致数据撕裂,即一个线程在读取或写入数据时被另一个线程打断,导致数据不一致。

  • 详细解释:例如,在32位机器上对64位整数进行读写操作不是原子的,可能导致线程读取到被部分更新的值。

  • 行动建议:确保用于线程同步的变量大小不超过CPU的字长,以保证原子性。

2. 内存排序影响并发程序的正确性:

  • 编译器和CPU为了优化性能,可能会对指令进行重排序,这可能导致多线程程序出现意想不到的结果。
  • 详细解释:例如,线程A先写入变量v,然后设置标志v_ready;线程B检查v_ready后再读取v。如果指令被重排序,线程B可能在v被写入之前就读取v_ready,导致错误。
  • 行动建议:使用C++的std::atomic类型,它可以防止编译器和CPU对原子操作进行重排序,确保多线程程序的正确性。

3. 顺序一致性是最强的内存排序模型:

  • 在顺序一致性模型下,所有线程的操作看起来就像按照某种全局顺序依次执行,每个线程的操作顺序与程序代码一致。
  • 详细解释:顺序一致性可以简化并发程序的推理,但性能开销也最大。
  • 行动建议:除非必要,否则不要总是使用顺序一致性,可以考虑使用更宽松的内存排序模型来提高性能。

4. 读-修改-写(RMW)操作是构建并发工具的关键:

  • RMW操作指的是读取一个值、修改它,然后将新值写回,这个过程是一个原子步骤。
  • 详细解释:常见的RMW操作包括交换(exchange)、测试并设置(test and set)、Fetch and…、比较并交换(compare and swap)。这些操作是构建锁、原子计数器等并发工具的基础。
  • 行动建议:理解各种RMW操作的用途和特点,根据实际需求选择合适的RMW操作来构建并发工具。

5. 内存排序可以进行更细粒度的控制:

  • C++的std::atomic提供了多种内存排序选项,包括顺序一致性(seq_cst)、获取(acquire)、释放(release)、宽松(relaxed)、获取-释放(acq_rel)和消费(consume)。
  • 详细解释:不同的内存排序选项提供了不同的同步保证和性能开销。例如,宽松排序只保证原子性,不保证顺序,性能开销最小;获取-释放排序用于实现线程间的同步,例如锁的获取和释放。
  • 行动建议:根据实际需求选择合适的内存排序选项,可以在保证程序正确性的前提下,最大限度地提高性能。

6. 避免伪共享以提高并发性能:

  • 伪共享指的是多个线程访问不同的变量,但这些变量位于同一个缓存行中,导致缓存行在多个CPU核心之间频繁传递,降低性能。
  • 详细解释:即使线程没有真正共享数据,伪共享也会导致性能下降。
  • 行动建议:可以通过填充缓存行来避免伪共享,即将原子变量填充到缓存行的大小,确保每个原子变量都位于独立的缓存行中。

7. volatile不是并发的解决方案:

  • volatile关键字用于告诉编译器,变量的值可能会被外部因素改变,不要对该变量进行优化。
  • 详细解释:volatile不能提供原子性和内存排序保证,不能用于构建并发工具。
  • 行动建议:不要使用volatile来进行线程间的通信,应该使用std::atomic。

问答:

Q: 什么是原子操作?为什么在并发编程中需要原子操作?

A: 原子操作指的是一个不可分割的操作,要么完全执行,要么完全不执行。在并发编程中,多个线程可能同时访问和修改共享数据,如果操作不是原子的,可能导致数据不一致或损坏。因此,需要使用原子操作来保证数据的一致性。

Q: 什么是内存排序?为什么在并发编程中需要关注内存排序?

A: 内存排序指的是CPU执行指令的顺序。为了优化性能,编译器和CPU可能会对指令进行重排序。在单线程程序中,指令重排序通常不会有问题,但在多线程程序中,可能会导致线程之间的数据竞争和错误。因此,需要关注内存排序,使用内存屏障等技术来保证多线程程序的正确性。

Q: 如何选择合适的内存排序模型?

A: 选择合适的内存排序模型需要在性能和正确性之间进行权衡。顺序一致性是最强的内存排序模型,但性能开销也最大;宽松排序只保证原子性,不保证顺序,性能开销最小;获取-释放排序用于实现线程间的同步。应该根据实际需求选择合适的内存排序选项,可以在保证程序正确性的前提下,最大限度地提高性能。

思维导图

目标读者

本文的目标读者是系统程序员、嵌入式系统开发者、实时系统开发者以及对并发编程感兴趣的读者。读者需要具备一定的C/C++编程基础,并了解基本的操作系统概念。通过阅读本文,读者可以深入理解并发编程的底层原理,掌握使用原子操作构建锁和无锁并发工具的技巧,并能够编写出更高效、更可靠的并发程序。

作者背景

Matt Kline 是一位系统程序员,对并发编程有深入的研究和实践经验。他致力于分享系统编程方面的知识,帮助开发者理解并发编程的底层原理和实用技巧。通过本文,他希望能够弥补系统程序员在并发编程知识上的不足,使他们能够编写出更高效、更可靠的并发程序。

历史背景

随着多核处理器和并发编程的普及,系统程序员面临着越来越多的并发挑战。传统的并发工具(如互斥锁和信号量)虽然能够解决一些问题,但在某些场景下(如嵌入式系统和实时系统)却显得力不从心。此外,编译器和硬件的优化也使得并发程序的行为变得难以预测。因此,系统程序员需要深入理解并发编程的底层原理,才能编写出高效、可靠的并发程序。本文正是在这样的背景下创作的,旨在帮助系统程序员应对并发挑战。

章节摘要

音频

Comming Soon...