Thread

2026/07/15

并行/并发

cpu是如何处理这么多进程的?

每个进程内部都有很多线程,cpu的核心会轮流执行每个线程一段时间,然后中断去执行别的线程,因为这个时间极短,所以看起来像是在一直运行,这就是并发

并发:是逻辑上的“同时处理”,本质是轮流切换(看起来像同时做)。

并行:是物理上的“同时执行”,本质是一起开工(真正的同时做)。

并发是关于如何写代码:用多线程、协程、异步回调去处理多个独立的业务流。

并行是关于如何运行代码:你的程序是否真的在利用多核 CPU 同时运算。

线程模型

大部分编程语言里的线程都不等于操作系统的线程

内核线程 (Kernel Thread):这是操作系统(如 Windows、Linux)真正管理、调度的执行单元。创建和切换它开销很大,需要陷入内核态(系统调用)。

用户态线程 (User-level Thread):这是编程语言或运行时(Runtime)在用户空间自己管理和调度的“虚拟线程”。我们平时在代码里 new Thread() 或 go func() 创建的大多属于这一类。

  1. 1:1 模型(直接映射)

语言创建的每个线程,都直接对应一个操作系统内核线程。

代表:Java (传统线程)、Rust (标准库线程)、C++ (std::thread)。

特点:实现简单,能充分利用多核 CPU。但线程数量受限(通常只能创建几千个),创建和切换成本较高。
  1. N:1 模型(多对一)

语言在用户空间管理多个“协程”或“轻量级线程”,但底层只用一个内核线程来跑。

代表:早期的 Python (Gevent)、Node.js (异步回调)。

特点:切换极快,能轻松创建成千上万个任务。但无法利用多核 CPU 并行计算(只能并发),一旦这个内核线程阻塞,整个程序都会卡住。
  1. M:N 模型(多对多,最灵活)

语言运行时会创建 M 个用户线程,然后动态地绑定到 N 个内核线程上(N < M)。

代表:Go (Goroutine)、Erlang (进程)、Java (虚拟线程,即 Project Loom)。

特点:可以创建海量(百万级)的“线程”,切换开销极小,同时又能充分利用多核 CPU。这是目前公认最先进的线程模型。

既然操作系统有现成的线程,为什么语言还要自己搞一套呢?核心原因有两点:

切换成本的天壤之别:

    操作系统切换线程(上下文切换)需要保存大量寄存器、切换内存映射,耗时在微秒级。

    编程语言切换自己的用户态线程,通常只需要保存几个程序计数器和栈指针,耗时在纳秒级,效率高几个数量级。

海量并发能力的差异:

    一个操作系统线程默认占用约 8 MB 的栈内存,如果创建 1 万个线程,仅内存就需要 80 GB,根本无法实现。

    像 Go 的 Goroutine,初始栈只有 2 KB,并且会动态扩缩容。创建 10 万个 Goroutine 对现代服务器来说轻轻松松。

https://docs.gitlab.com/user/project/merge_requests/reviews/stacked_merge_requests/