Jakob Jenkov 2021-11-02
同线程模型(same-threading)是一种并发模型,它将单线程系统横向扩展为 N 个并行运行的单线程系统。
一个“同线程”系统并不是纯粹的单线程系统,因为它包含多个线程。但——每个线程都像一个独立的单线程系统那样运行。因此我们使用“同线程”(same-threaded)而非“单线程”(single-threaded)这一术语。
为什么使用单线程系统?
你可能会疑惑,如今为何还有人设计单线程系统?
单线程系统之所以流行,是因为其并发模型比多线程系统简单得多。单线程系统不与其他线程共享任何状态(对象/数据),这使得单个线程可以安全地使用非并发数据结构,并更好地利用 CPU 及其缓存。
然而,单线程系统无法充分利用现代 CPU 的全部性能。现代 CPU 通常拥有 2、4、6、8 甚至更多核心,每个核心都相当于一个独立的 CPU。而单线程系统只能使用其中一个核心,如下图所示:

同线程模型:横向扩展的单线程
为了充分利用 CPU 的所有核心,我们可以将单线程系统横向扩展,使其运行在整个计算机上。
每个 CPU 核心对应一个线程
同线程系统通常在计算机的每个 CPU 核心上运行一个线程。例如,如果一台计算机有 4 个 CPU 核心,那么通常会运行 4 个同线程系统的实例(即 4 个独立的单线程系统)。原理如下图所示:

无共享状态
同线程系统从表面上看类似于传统的多线程系统,因为内部也运行了多个线程。但两者之间存在一个微妙却关键的区别:
同线程系统中的线程不共享状态。
也就是说,线程之间没有并发访问的共享内存,也没有用于在线程间传递数据的并发数据结构。这一区别如下图所示:

正是由于缺乏共享状态,每个线程的行为就像一个真正的单线程系统。但由于整个系统包含多个线程,因此严格来说它不是“单线程系统”。为了更准确地描述这种架构,我将其称为“同线程系统”,而不是“采用单线程设计的多线程系统”。“同线程”这个说法更简洁、更易理解。
简而言之,“同线程”意味着:
- 数据处理始终在同一个线程内完成;
- 系统中的线程不会并发地共享数据。
这种模型有时也被称为“无共享状态并发”(no shared state concurrency)或“分离状态并发”(separate state concurrency)。
负载分配
显然,同线程系统需要将工作负载合理分配给各个单线程实例。如果只有一个线程在工作,那么整个系统实际上仍是单线程的。
具体的负载分配方式取决于你的系统设计。以下是一些常见模式:
单线程微服务
如果你的系统由多个微服务组成,每个微服务都可以以单线程模式运行。当你将多个单线程微服务部署在同一台机器上时,每个微服务可以在一个 CPU 核心上运行一个线程。
微服务本质上不共享数据,因此非常适合采用同线程架构。
带分片数据的服务
如果你的系统确实需要共享数据(例如数据库),你可以考虑对数据库进行分片(sharding)。分片是指将数据划分到多个数据库中,通常按某种逻辑(如用户 ID、租户 ID 等)将相关数据集中存储在同一数据库中。例如,某个“所有者”实体的所有数据都存入同一个分片数据库。
注:分片技术超出了本教程的范围,请自行查阅相关资料。
线程通信
如果同线程系统中的线程需要通信,它们通过消息传递(message passing)实现。
例如,线程 A 想向线程 B 发送消息,它可以生成一条消息(字节序列)。线程 B 接收后会复制该消息,然后读取副本。通过复制,线程 B 确保线程 A 在其读取过程中无法修改原始消息。一旦复制完成,该副本对线程 A 不可见。
线程间的消息通信如下图所示:

通信通道可以是队列、管道、Unix 套接字、TCP 套接字等,具体取决于系统需求。
更简单的并发模型
在同线程系统中,每个线程内部都可以按照单线程的方式进行设计和实现。这意味着其内部并发模型远比共享状态的多线程系统简单——你无需担心并发数据结构及其可能引发的各种并发问题(如竞态条件、死锁等)。
图示对比
以下是单线程、多线程和同线程系统的示意图,帮助你直观理解三者的区别:
单线程系统

传统多线程系统(线程共享数据)

同线程系统(两个线程,各自拥有独立数据,通过消息通信)
