并发——整章是 Go 的教材,只有「线程」两个字是真的¶
这一章讲一件事: 原书第九章教并发编程。它是全书跨语言缝合最彻底的一章—— goroutine、channel、
<-运算符是 Go 的招牌;std.sync.mutex.New()、std.chan.new这些 API 是编的;async/await 一节教的是一门语言正在移除的功能。 我们的拆解照例两栏对照;因为这一章的「正主」太明确,你顺带会得到一份 「Go 并发模型速成」。
1. 这一章讲什么¶
原书小节 内容 真假
──────────────────────────────────────────────────────
9.1 线程与 goroutine 线程 + Go 的 goroutine 线程概念真,API 假;goroutine 是 Go
9.2 channel 通信 chan、<-、close 整节是 Go
9.3 同步原语 互斥锁、信号量、条件变量 概念真,API 全编
9.4 原子操作 load/store/add 等 概念真,API 编;真实是 std.atomic.Value
9.5 async/await 异步函数、await、suspend 语言已移除这对关键字
图说:五节五假(概念层除外)。这是全书含假率最高的整章。
先就地解释本章的两个基本概念,后面的对账都建立在这两个词上:
并发(concurrency)指一个程序里多件事交替推进(不一定是同时, 而是「这件事等输入的空档,去做那件事」); 并行(parallelism)指多件事真的同时发生(多核 CPU 各跑一件)。 日常说「并发编程」通常两个意思都包,原书第 09 章讲前者、第 10 章讲后者,分法是清楚的。
2. 顶层全景:主线例子的两世——两个 goroutine 计数¶
拿书 9.4 节的例子当主线,它想让两个并发任务各给同一个计数器加一次数:
// 书里写的(9.4 节):
var atomic_int = std.atomic.new(i32, 0);
go {
std.atomic.add(&atomic_int, 1);
}
go {
std.atomic.add(&atomic_int, 2);
}
std.debug.print("atomic_int: {}\n", .{std.atomic.load(&atomic_int)});
走查(按书的说法) 走查(真实 Zig 编译器视角)
──────────────────────────────────────────────────────────
① std.atomic.new(i32, 0) 编译错误:std.atomic 下没有 new 这个函数
造一个原子整数(初值 0) (真实:var n = std.atomic.Value(i32).init(0);)
② 两个 go 块并发执行 编译错误:go 不是 Zig 关键字
③ 各自原子加 1 和 2 ③ 的两个调用也都不存在
④ 打印 → 3 书断言最终结果「一定是 3」——这个断言本身
才是真问题:就算 API 都存在,
没有 join/等待,主线程可能先打印,结果未必是 3
图说:四步全编,而且第 ④ 步的「一定是 3」连概念都不严谨——
并发程序不等待就直接读结果,是并发编程的第一课就要讲的错误。
这个「一定是 3」值得停一下:书里的两个 go 块和主线程之间没有任何汇合点
(join,等别人做完),主线程随时可能先跑到打印。真实教材(无论 Go 还是别的)
讲到这个例子的第一修正就是「先等两个任务都完成再打印」。
书把它原样写成「will be 3」——说明这段文字生成时,
并没有一个并发模型在背后支撑,只有「原子操作」这个词条的形状。
3. 核心原理之一:线程与 goroutine——Go 的并发世界观¶
书 9.1 节教两种「并发单位」:线程(操作系统管的执行流)
和 goroutine(「由 Zig 运行时管理的轻量线程」,go 关键字启动)1。
goroutine 这一段值得完整对账,因为它是 Go 语言的世界观被整体移植:
| 书教的「Zig」 | 真相 |
|---|---|
go std.debug.print(...) 启动 goroutine |
Go 的语法,Zig 没有 go 关键字 |
| goroutine 由「Zig 运行时」管理 | Go 有运行时,Zig 没有运行时——这正是 Zig 的卖点之一(小 runtime、无隐藏机制) |
| 「Zig 没有内置方法等待 goroutine,用 channel 同步」 | Go 的标准答案原样搬来;Zig 的真实答案是 std.Thread.spawn 返回句柄,用 .join() 等 |
std.os.spawn(.{}, fn () void {...}) 创建线程 |
API 编造;真实是 std.Thread.spawn(.{}, worker, .{}) 且线程函数必须是具名 fn,书那种内联匿名 fn 写法也不是 Zig |
「Zig 没有运行时」是这一章的钥匙: goroutine 之所以是 Go 的, 因为 goroutine 需要一个运行时(语言自带的调度程序)来管理成千上万 个轻量线程;而 Zig 从设计宣言起就拒绝运行时—— 官方主打「minimal runtime」「no hidden control flow」2。 一个「有 goroutine 的 Zig」等于一个「有跑车的自行车」: 不是好坏问题,是范畴问题。书把 Go 的整套并发世界观(运行时、go、channel、 「用通信来共享内存」的格言)搬进来,等于宣布它根本没读过 Zig 的设计宣言。
4. 核心原理之二:channel——<- 是 Go 的标点¶
9.2 节教 channel(信道):std.chan.new(i32) 创建、<- 发送、<-c 接收、
std.chan.close 关闭,例子是「一个 goroutine 发 42、主线程收 42」3。
书教的通信原语 正主 真实 Zig 里的对应思路
─────────────────────────────────────────────────────────
chan 类型 + <- 收发 Go 无内置信道;用 Mutex + Condition 或
close(channel) Go 队列结构自己搭;stdlib 的并发容器
「channel 用于同步」 Go 格言 (如 std.Thread.Pool)提供现成方案
std.chan 模块 不存在 ——
图说:连「用通信代替共享内存」这句口号都是 Go 的(CSP 模型)。
先解释 channel(信道):一条带类型的管道,一个执行流往里面放值、 另一个执行流取;取的时候管道空就等着,放的时候有人等就直接交接—— 它既是通信工具也是同步工具。Go 语言的并发哲学就是「通过通信来共享内存」, 信道是它的中心;而 Zig 的哲学(官方口径)是没有这种中心原语—— 原语指语言直接提供、不可再拆的基础操作件; 给你线程、锁、条件变量、原子这些底层件,信道这类东西要用 std 的 容器加锁自己组,或者用 std.Thread.Pool(线程池,一组复用的工作线程)2。 两种哲学都成立、各有拥趸——但书只讲了其中一种,还署错了名字。
5. 核心原理之三:同步原语——概念全真,API 全编¶
9.3 节教三件套:互斥锁(mutex,同一时刻只允许一个执行流进入的锁)、 信号量(semaphore,带计数的水闸,允许多个但有限个执行流通过)、 条件变量(condition variable,「某条件成立了」的广播通知)4。 这三个概念是并发编程的通用词汇,每个讲法(锁要配对加解锁、信号量 wait/signal、 条件变量和锁配合使用)都是标准教材口径——概念层这一节可以打全对。
API 层照例全编:std.sync.mutex.New()、std.sync.mutex.lock(mutex)、
std.sync.semaphore.New(1)、std.sync.cond.New()、
std.sync.cond.wait(mutex, cond)——这个 std.sync 名字空间在真实 Zig
标准库里不存在。真实的对应物:std.Thread.Mutex(类型,方法 .lock()/.unlock())、
std.Thread.Semaphore、std.Thread.Condition2。
形状差异有信息量:真实 API 是「对象.方法()」(锁自己知道自己怎么锁),
书教的是「模块.动词(锁)」——后者更像从函数式风格教材里搬的形状。
顺带一提:9.3 的例子都用 go { ... } 启动任务,一个锁相关的经典场景
(两个 goroutine 各加一次 1,最后打印 shared_data)同样是没有汇合点
就打印——书里这类「不等待就读结果」的例子,本章出现了三次。
6. 核心原理之四:async/await——教一门语言正在删除的功能¶
9.5 节教异步:fn async_function() suspend i32(suspend 类型)、
await async_function()、异步 I/O(await file.readSlice(100))5。
这一节在时间线上的位置很微妙:书出版于 2024 年 10 月; 真实 Zig 的 async/await 自 0.10(2022)起因自举编译器未完成而缺失, 之后多年「承诺回归」一再跳票,官方 2023 年 7 月的公告承认「我说了九个月要交付, 今天食言」;最终 0.15.1(2025 年 7 月)发行说明一锤定音: 「async 和 await 关键字已被移除……语言里将不再有 async/await 关键字, 它们会以标准库 Io 接口的一部分存在」6。
也就是说,书教的是一对正在被语言永久移除的关键字,而且它教的语法
(suspend i32 返回类型)连当年的 Zig 都没这么写过。
这一节是全书「出版即过时」的最极端样本——不是过时一个版本,
是教了一个人家公开宣布不要了的东西。
要解释的词:async/await 是一对「非阻塞」标记——async fn 表示
这个函数可能中途挂起让出线程,await 表示在这里等它挂起的事办完;
它解决的是「一个线程等网络输入时闲着浪费」的问题,代价是
「函数分两种颜色」的著名争议(调用处必须知道要不要 await)。
7. 作者的判断与证据¶
- 经核对可用(概念层): 线程与 goroutine 的概念区分、互斥锁/信号量/条件变量的用途、 原子操作「不可分割」的定义、channel 的发收语义、竞态条件(race condition, 多执行流抢改同一份数据造成结果取决于时序)这一章反复提到的风险词145。
- 经核对不可用(机制层):
go关键字、goroutine 与「Zig 运行时」、std.chan、<-、std.sync.*全套、std.atomic.new/add/load、 全部 async/await 语法35。 - 证据形态: 本章的 API 命名有一个统一的编造风格——「std.」前缀 +
小写模块名 + 句点动词(
std.chan.new、std.sync.mutex.lock)—— 这与真实 std 的「类型.方法」风格系统性相反,像是从「语言无关的伪代码」 模板直接套出来的。
判断(我们的,不是书里的): 这一章的拼接来源最可能是 Go 教材: 因为 goroutine + channel + 「用通信共享内存」是 Go 的完整世界观, 不是零散语法;零散语法会拼错世界观,拼不出一个世界观。 如果错,会错在: 也可能来自某本「并发通用教材」把 Go 当示例语言—— 但这同样意味着书里没有一行内容是关于 Zig 的。
8. 边界与局限¶
- 书没讲真实 Zig 并发的入门第一课:
std.Thread.spawn+.join()的完整 hello-world(真实官方风格),以及数据竞争下为什么不能直接共享变量。 - 书没讲 Zig 生态真实的并发答案正在向「事件循环/Io 接口」走 (0.16 的 std.Io.Threaded / std.Io.Evented)——这是 2026 年的前沿, 书出版时还没有,不怪它;但它教的 async 路线与此无关。
- 条件变量的例子(9.3 节)逻辑上还有一个真 bug:检查
data_ready的 while 循环里,若信号在 wait 之前到达会丢——这是教材级的微妙处,书没处理。
9. 真实的 Zig 并发第一课(书里完全没有的版本)¶
作为对照,把真实 Zig 并发的最小完整程序写出来——书的第九章没有一行 接近它:
const std = @import("std");
fn worker(id: usize) void {
std.debug.print("worker {} 开始\n", .{id});
}
pub fn main() !void {
// spawn:建线程;.{ } 是配置项(这里用默认);
// worker 是函数名,.{0} 是传给它的参数包
const t1 = try std.Thread.spawn(.{}, worker, .{1});
const t2 = try std.Thread.spawn(.{}, worker, .{2});
t1.join(); // 汇合点:等 t1 做完
t2.join(); // 等 t2 做完——第 2 节那个「一定是 3」的例子里缺的就是这两行
}
三个真实细节,对照书的三个虚构:
- spawn 返回句柄,join 就是汇合点。 书的世界里「等 goroutine 要靠
channel」(Go 的答案);真实 Zig 的世界里,等线程就是
.join()一个方法。 - spawn 也可能失败(系统建不出线程),所以要
try—— 第 06 章「每个分配点都要面对失败」的同款纪律,在并发这里再来一遍。 - 没有运行时: 线程直接由操作系统调度,Zig 编译器不在中间插手。 这就是「no hidden control flow」在并发上的样子——书教的 「Zig 运行时在幕后调度 goroutine」恰好是这门语言拒绝的事。
判断(我们的,不是书里的): 只学真实 Zig 的并发(线程 + join + 锁 + 原子),一小时内能写出正确程序;照书学完 goroutine/channel, 你得到的是一门 Go 的二手入门——然后发现 Zig 里一个都不能用。 如果错,会错在: 如果未来 Zig 的 Io 接口成熟到「看起来像 goroutine」 (事件化后端 + 协程式写法),书教的图景会偶然部分成立——但那是 标准库层的机制,仍然不是语言层的 go/channel,而且方向相反: Zig 是用库模拟协程,不是用运行时原生提供。
这一节的真实代码还有一个值得带走的细节:try std.Thread.spawn(...)
里的 spawn 会失败,而书的 std.os.spawn 例子里没有任何失败处理
(连 try 的影子都没有)1。全书所有「可能失败」的操作
(分配、建线程、打开文件)在书的代码里都表现得永远不会失败——
这是 C 教材的惯性(C 里 malloc 失败了也常常没人查),
也是这本书的来源指纹:它抄来的每一门语言里,恰好 C 系的素材占比最大。
10. 可带走的¶
- 并发 vs 并行:交替推进 vs 真正同时;原书两章的分法是对的。
- goroutine、channel、
<-、go关键字——全是 Go 的。 「有运行时的 Zig」在范畴上不成立。 - 锁/信号量/条件变量的概念和用法描述通用可信,但
std.sync.*API 不存在;真实是 std.Thread.Mutex 等「类型.方法」风格。 - 原子操作「不可分割」的概念真;
std.atomic.new/add编的,真实是std.atomic.Value(i32). - 本章所有「最后打印结果」的例子都缺汇合点——并发程序不 join 就读,结果未必如书所言;这是比语法更值得记住的坑。
- async/await 已被 Zig 语言永久移除(0.15.1 官方说明),替代方向是标准库 Io 接口——书教了一门语言公开宣布不要的功能。
- 想见识真实 Zig 并发:
std.Thread.spawn/join起步,线程池用 std.Thread.Pool——第 9 节那个 20 行程序就是真第一课。
11. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 线程创建与 join | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:13(搜「std.os.spawn」) · text/11-ch09-chapter-9-concurrent-programming.txt:43(搜「std.os.wait」) |
| goroutine 与 go 关键字 | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:75(搜「managed by the Zig runtime」) · text/11-ch09-chapter-9-concurrent-programming.txt:79(搜「go keyword」) · text/11-ch09-chapter-9-concurrent-programming.txt:99(搜「does not have a built-in way」) |
| channel 与 <- 运算符 | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:109(搜「chan keyword」) · text/11-ch09-chapter-9-concurrent-programming.txt:119(搜「std.chan.new」) · text/11-ch09-chapter-9-concurrent-programming.txt:129(搜「<- operator」) · text/11-ch09-chapter-9-concurrent-programming.txt:177(搜「close method」) |
| 互斥锁、信号量、条件变量 | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:219(搜「Mutexes」) · text/11-ch09-chapter-9-concurrent-programming.txt:229(搜「std.sync.mutex.New」) · text/11-ch09-chapter-9-concurrent-programming.txt:275(搜「Semaphores」) · text/11-ch09-chapter-9-concurrent-programming.txt:327(搜「Condition Variables」) |
| 原子操作 | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:393(搜「single, indivisible unit」) · text/11-ch09-chapter-9-concurrent-programming.txt:407(搜「std.atomic.new」) · text/11-ch09-chapter-9-concurrent-programming.txt:461(搜「final value of atomic_int will be 3」) |
| async/await 与 suspend | Chapter 9: Concurrent Programming | text/11-ch09-chapter-9-concurrent-programming.txt:475(搜「async and await keywords」) · text/11-ch09-chapter-9-concurrent-programming.txt:479(搜「suspend type」) · text/11-ch09-chapter-9-concurrent-programming.txt:497(搜「await keyword」) · text/11-ch09-chapter-9-concurrent-programming.txt:539(搜「asynchronous I/O」) |
-
出处:「Chapter 9: Concurrent Programming」第 7–101 段(
text/11-ch09-chapter-9-concurrent-programming.txt:13,搜「std.os.spawn」;:75,搜「managed by the Zig runtime」;:79,搜「go keyword」;:99,搜「does not have a built-in way」)。 ↩↩↩ -
补充(不在书里,来自权威外部资料):Zig 主打无隐藏控制流、极小运行时;并发原语在 std.Thread 下(Mutex/Condition/Semaphore/Pool);标准库不依赖 libc、可用于 freestanding。来源:《Overview — Zig Programming Language》https://ziglang.org/learn/overview/(查阅于 2026-10-19)。 ↩↩↩
-
出处:「Chapter 9: Concurrent Programming」第 103–211 段(
text/11-ch09-chapter-9-concurrent-programming.txt:105,搜「fundamental mechanism for communication」;:109,搜「chan keyword」;:129,搜「<- operator」;:177,搜「close method」)。 ↩↩ -
出处:「Chapter 9: Concurrent Programming」第 213–389 段(
text/11-ch09-chapter-9-concurrent-programming.txt:219,搜「Mutexes」;:275,搜「Semaphores」;:327,搜「Condition Variables」)。 ↩↩ -
出处:「Chapter 9: Concurrent Programming」第 473–557 段(
text/11-ch09-chapter-9-concurrent-programming.txt:475,搜「async and await keywords」;:479,搜「suspend type」;:497,搜「await keyword」;:549,搜「std.fs.open」)。 ↩↩↩ -
补充(不在书里,来自权威外部资料):《Zig 0.15.1 Release Notes》语言变更:「async and await keywords removed……it is settled that there will not be async/await keywords in the language. Instead, they will be in the Standard Library as part of the Io Interface」;2023 年 7 月官方公告《The Upcoming Release Postponed Two More Weeks and Lacks Async Functions》承认该功能多次跳票。来源:https://ziglang.org/download/0.15.1/release-notes.html、https://ziglang.org/news/0.11.0-postponed-again/(查阅于 2026-10-19)。 ↩