并行——std.parallel 不存在,SIMD 倒是 Zig 的真招牌
并行——std.parallel 不存在,SIMD 倒是 Zig 的真招牌
这一章讲一件事: 原书第十章教并行编程(让多核同时干活)。 它教的四个工具里,三个(std.parallel、任务池、并行 map)建立在虚构模块上; 唯一撞对方向的 SIMD,书给的 API 也是编的,真实 Zig 的写法更优雅—— 把一个向量当成普通类型,加就用
+。 这一章读法的重点:概念地图(哪些并行模式存在)通用可信,全部机制要重学。
1. 这一章讲什么
原书小节 内容 真假
──────────────────────────────────────────────────────
10.1 并行循环 std.parallel.for 三倍循环 模块与 API 假;「按核分迭代」概念真
10.2 任务并行 channel 任务池、worker 建立在 9 章虚构上,整节假
10.3 SIMD 指令 std.simd.vec/add/mul 方向真,API 假;真实是 @Vector
10.4 并行算法 map-reduce、分治等名录 概念真,例子(std.parallel.map)假
图说:四节的概念骨架是一张标准的并行模式地图——这张地图可以带走;
地图上标注的全部「Zig 地名」要撕掉重贴。2. 顶层全景:主线例子走查——数组每个元素乘 2
书 10.1 节的例子想让数组 [1,2,3,4,5] 的每个元素乘 2:
// 书里写的:
var array = [1, 2, 3, 4, 5];
std.parallel.for(0, array.len, fn (i: usize) void {
array[i] *= 2;
});
std.debug.print("array: {any}\n", .{array}); 走查(按书的说法) 走查(真实 Zig 视角)
──────────────────────────────────────────────────────────
① std.parallel.for(起始, 结束, 函数) 编译错误:std 下没有 parallel 模块
把 0..5 的迭代分给各核, (真实:并发执行要自己用 std.Thread,
每个 i 调一遍那个函数 或第三方池;stdlib 没有「并行 for」)
② array[i] *= 2 就地改数组 概念真——但书的闭包式内联 fn 写法
Zig 不支持(函数不能这样捕获外部变量,
见第 05 章闭包一节)
③ 打印 {any} 整个数组 打印语法本身是真的(std.debug.print 的
{any} 格式符通用)
图说:三步一步虚构、一步能力错位、一步全真。比例和全章一致。第三步那个 {any} 是顺手要认的真词条:std.debug.print 里 {s} 打字符串、
{} 打单个值、{any} 打一个结构体或数组的「随便什么样都 dump 出来」格式——
这是全书出现多次的真用法,可以带走。
3. 核心原理之一:并行循环与任务池——「按核分工」的概念真,模块假
10.1 节的概念是真实的并行编程常识:循环的各次迭代(循环里的每一轮) 若互不相干,就可以分给多个核同时跑;但共享数据的写入要小心竞态 (第 08 章讲过的 race condition),书也给了这条告诫1。
10.2 节的概念也通用:任务并行(task parallelism)指把一个大活拆成 许多独立小任务,交给一个任务池(一组复用的工作线程,task pool) 排队执行,免得为每个任务新建线程的开销;生产者-消费者(producer-consumer, 一方产任务、一方消费)是最常见的配法2。这两个概念在任何语言的 并行课里都长这样,书讲清楚了。
假的是实现:书用 std.chan 建任务通道、用 go 起 worker(第 08 章已拆穿),
还写了一段 task_chan <- fn () i32 { return i * i; } 的代码——
函数当值塞进信道。这一节从地基(module 名)到脚手架(go、channel)到例子
(闭包捕获 i)没有一层是真的,是全书单节依赖虚构最多的一章。
真实 Zig 的对应物,值得一提:标准库有 std.Thread.Pool(线程池),
第三方生态有成熟的并行库;但整体上说,真实 Zig 社区的并行实践
(2024 年前后)主要是「自己用 std.Thread 组」,
没有一个官方 std.parallel——这本身就是个事实值得记住:
Zig 生态在并行这个领域是「给零件不给整车」。
4. 核心原理之二:SIMD——书唯一撞对方向的招牌
10.3 节讲 SIMD。先把概念讲透,因为这一节值得当真知识带走:
SIMD(单指令多数据,single instruction multiple data)指 CPU 里一类 「一次算一整排数」的指令。普通加法一次算一对数;SIMD 加法一条指令 同时给 4 个、8 个甚至 16 个数对各自相加——这一整排成组的数, 在硬件术语里就叫「向量」(vector)。对图像处理、科学计算、 机器学习这类「对海量数据做同一种运算」的场景,这是免费的数倍到数十倍加速。 书里那个例子(两排四个浮点数对应相加)选的正是 SIMD 最经典的用法3。
书教的 API:std.simd.vec(f32, [1.0, 2.0, 3.0, 4.0]) 造向量,
std.simd.add(a, b)、std.simd.mul(a, b) 做运算——这些函数不存在。
真实 Zig 的写法是这一章最大的惊喜,值得完整展示:
// 真实 Zig:SIMD 向量是语言类型,不是库函数
const V = @Vector(4, f32); // 定义一个「4 个 f32」的类型
const a: V = .{ 1.0, 2.0, 3.0, 4.0 };
const b: V = .{ 5.0, 6.0, 7.0, 8.0 };
const c = a + b; // 普通的加号,硬件级并行
std.debug.print("c: {any}\n", .{c}); // 打印 { 6.0, 8.0, 10.0, 12.0 }官方文档的原话是:「Zig directly exposes a SIMD vector type,
making it easy to write portable vectorized code」——
SIMD 类型直接暴露为语言类型,用普通运算符,不用 intrinsics
( intrinsics 指编译器提供的、名字又长又怪的专用函数,是 C/C++ 里用 SIMD 的痛苦来源)4。
对照书教的 std.simd.add(a, b):真实 Zig 用 a + b。
这个对照浓缩了两种语言的差距——C/C++ 把 SIMD 当「特殊能力要用特殊函数」,
Zig 把它当「普通类型该有普通运算」。
顺带修正书的一处细节:它说 std.simd.vec(f32) 是「4 元素向量」3——
真实 Zig 的向量长度是类型的一部分(@Vector(4, f32) 与 @Vector(8, f32)
是两种类型),长度不限于 4,且很多硬件路径上长度就是向量寄存器宽度。
5. 核心原理之三:并行算法名录——地图可信,路标全错
10.4 节列了四种并行模式:map-reduce(对集合每个元素算一遍再合并)、 分治(divide and conquer,拆小递归解决再合并)、数据并行(数据切块各核同操作)、 任务并行(独立任务分发)5。这四个名字是并行计算的通用词汇, 在任何一门语言的并行课里都长这样——名录可以抄。
书给的 std.parallel.map(array, fn...) 例子当然是假的(同一个虚构模块),
但 map 这个模式本身值得用真实 Zig 再写一遍,因为它展示了真实生态的姿势:
map 模式(概念) 真实 Zig 的姿势
────────────────────────────────────────────────────
对数组每个元素算 x*x std.Thread.Pool 或手写 spawn,
收集成新数组 每个元素一个小任务,join 后拼结果
「谁能给我一行 map?」 「stdlib 给零件,自己组」——这正是
第 10.2 节那句「Zig 没有内置任务并行」
的真实含义(书这句反而说对了)
图说:模式是永恒的,地名是暂时的;书的地名从开头就是假的。注意:书在 10.2 节有一句真话值得单独记账——「Zig does not have built-in support for task-based parallelism」2。这句对 2024 年的 Zig 基本成立 (官方只有零件);整本书在描述「Zig 没有什么」时罕见地准确, 在描述「Zig 有什么」时系统性失实——这个不对称本身,是识别这类书的又一特征。
再说透一层「零件与整车」的分别:真实 Zig 把线程、锁、条件变量、
原子、线程池都给你,但不替你决定并行策略(哪段循环切多少份、
任务队列多长、负载怎么均衡)。这些是性能问题的真正分水岭,
两种语言都写不出「自动正确」的并行 for——书教的 std.parallel.for
若真存在,也只是把「分块」这一小步自动化了,
竞态、粒度、假共享这些真问题一个也不会替你解决。
所以「Zig 没有内置并行循环」不是缺陷,是诚实:
并行化的难点本来就不在那一行调用上。
6. 作者的判断与证据
- 经核对可用: 并行的四种模式名录、SIMD 的概念与适用场景、
「并行要小心竞态、要测试」的告诫、
{any}打印格式135。 - 经核对不可用:
std.parallel.for/map、std.simd.vec/add/mul/sub/div/cmp_gt、 channel 任务池的全部代码23。 - 证据形态: 10.2 节的任务池代码里混有两种互相矛盾的信道用法 (一处把函数塞进任务信道、一处 worker 读出后当函数调用、result 类型还对不上 i32), 这类「类型都对不齐」的代码只可能是未经任何编译检查生成的文本2。
7. 边界与局限
- 书没讲并行最关键的工程议题:如何选择并行粒度(任务太小吃调度开销)、 假共享(false sharing,两个核改同一块缓存行互相拖慢—— 缓存是 CPU 旁边那块比内存快得多的小容量高速存储,以「缓存行」为单位整块搬运)等——真实并行课的主角, 书一概没有。
- SIMD 一节没提对齐要求(向量加载对内存对齐敏感)和降标量(scalar fallback, 旧 CPU 没有 SIMD 指令时怎么办)——书只说「注意对齐」一句带过3。
- 真实 Zig 在 2026 年的前沿(std.Io 接口、线程池演进)超出书的视野, 不怪书,但意味着照书学完你对真实生态的并行实践仍一无所知。
8. 可带走的
- 并行循环 / 任务池 / map-reduce / 分治 / 数据并行 / 任务并行——这张模式地图通用,可抄。
std.parallel不存在;真实 Zig 并行是「std.Thread 给零件、自己组装」,官方没有并行 for。- SIMD 概念是真的且是 Zig 的招牌之一:一次算一整排数,免费加速数倍。
- 真实 Zig 的 SIMD 是
@Vector(4, f32)语言类型,加法用普通+——不用std.simd.add这类编出来的函数。 std.debug.print的{any}格式符是真的,打数组/结构体都用它。- 书说「Zig 无内置任务并行」这句罕见地准——记住这个不对称:书说「没有什么」常可信,说「有什么」要逐条核。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 并行循环 std.parallel.for | Chapter 10: Parallel Programming | text/12-ch10-chapter-10-parallel-programming.txt:11(搜「std.parallel module」) · text/12-ch10-chapter-10-parallel-programming.txt:33(搜「array of integers」) · text/12-ch10-chapter-10-parallel-programming.txt:47(搜「divides the loop iterations」) |
| 任务并行与任务池 | Chapter 10: Parallel Programming | text/12-ch10-chapter-10-parallel-programming.txt:61(搜「task pool」) · text/12-ch10-chapter-10-parallel-programming.txt:65(搜「does not have built-in support」) · text/12-ch10-chapter-10-parallel-programming.txt:105(搜「Task Pool Implementation」) |
| SIMD 概念与 std.simd API | Chapter 10: Parallel Programming | text/12-ch10-chapter-10-parallel-programming.txt:183(搜「Single Instruction, Multiple Data」) · text/12-ch10-chapter-10-parallel-programming.txt:187(搜「std.simd module」) · text/12-ch10-chapter-10-parallel-programming.txt:211(搜「SIMD vector types」) |
| 并行算法名录与 map 例子 | Chapter 10: Parallel Programming | text/12-ch10-chapter-10-parallel-programming.txt:269(搜「Map-Reduce」) · text/12-ch10-chapter-10-parallel-programming.txt:287(搜「std.parallel.map」) · text/12-ch10-chapter-10-parallel-programming.txt:307(搜「overhead associated with parallelizing」) |
Footnotes
-
出处:「Chapter 10: Parallel Programming」第 5–57 段(
text/12-ch10-chapter-10-parallel-programming.txt:7,搜「execute multiple iterations」;:47,搜「divides the loop iterations」;:53,搜「race conditions and deadlocks」)。 ↩ ↩2 -
出处:「Chapter 10: Parallel Programming」第 59–179 段(
text/12-ch10-chapter-10-parallel-programming.txt:61,搜「task pool」;:65,搜「does not have built-in support」;:75,搜「std.chan.new」;:107,搜「pool of goroutines」;:167,搜「worker goroutines read tasks」)。 ↩ ↩2 ↩3 ↩4 -
出处:「Chapter 10: Parallel Programming」第 181–259 段(
text/12-ch10-chapter-10-parallel-programming.txt:183,搜「Single Instruction, Multiple Data」;:187,搜「SIMD intrinsics」;:197,搜「std.simd.vec」;:211,搜「SIMD vector types」;:255,搜「fixed vector width」)。 ↩ ↩2 ↩3 ↩4 ↩5 -
补充(不在书里,来自权威外部资料):「Zig directly exposes a SIMD vector type, making it easy to write portable vectorized code」。来源:《Overview — Zig Programming Language》https://ziglang.org/learn/overview/(查阅于 2026-10-19)。 ↩
-
出处:「Chapter 10: Parallel Programming」第 261–311 段(
text/12-ch10-chapter-10-parallel-programming.txt:269,搜「Map-Reduce」;:271,搜「Divide and Conquer」;:287,搜「std.parallel.map」;:307,搜「overhead associated」)。 ↩ ↩2