1 我们身边的并发编程

1.1 互联网的开始:Web1.0

从 PC 时代到互联网时代 (1990s)

  • Amazon (1994), Yahoo (1994), eBay (1995), Google (1998)
  • HTTP (对,没有 HTTPS), HTML,但没有 CSS
    • 中国互联网初代 “三巨头”:新浪、搜狐、网易诞生
    • <font>, <table>, vbscript 和切图工程师一统天下

1.2 Web2.0

Asynchronous JavaScript and XML (Ajax; ~1999)

  • 允许网页实现 “后台刷新”
    • 悄悄请求后端,然后更新 DOMTree
    • “应用” 可以做的,网页也都可以做了!
  • (你没看错,竟然不是 JSON)
    • 原因:后端 (Java) 应用广泛使用 XML

jQuery $ (2006)

  • 允许 Javascript 代码优雅地修改 DOMTree
  • $('h3').replaceWith('XXX');

从此,做“任何事”都只要浏览器就行

甚至诞生了 ChromeOS

  • HTML + CSS 构建应用的方便程度超过传统 GUI 编程
  • GTK, Qt, MFC 谁用谁知道

Web2.0时代的并发编程

Challenges

  • 线程 (在 1990s) 开销很大
  • 线程同步很难写对

Solution: Event-based concurrency (动态计算图)

  • 允许随时创建计算节点
    • 例如网络请求、定时器
  • 禁止计算节点并行
    • 网络访问占大部分时间;浏览器内计算只是小部分
  • 以事件为单位调度
    • 事件可以在浏览器里看到!

“Callback hell (回调地狱)”

  • 2024 年,教务系统里还能看到明文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$.ajax({
url: '/api/user',
success: function(user) {
$.ajax({
url: `/api/user/${user.id}/friends`,
success: function(friends) {
$.ajax({
url: `/api/friend/${friends[0].id}`,
...
});
},
error: function(err) {
...
}
});
},
...
});

ECMAScript 2015 (ES6)

  • 一统第三方库 “军阀混战” 的局面
  • 开源生态开始起飞

现代前端的代表作品

  • Angular, React, Vue
  • Express.js, Next.js
  • Bootstrap, Tailwindcss
  • Electron (vscode)
    • 2016 年,还用先烈 Github Atom 做过实验

Promise: 描述动态计算图

1
2
3
4
5
useEffect(() => {
fetch(`/api/localhost/?action=demo&path=${path}`)
.then(response => response.json())
.then(fetchedData => setData(fetchedData))
}, []);
1
2
3
4
5
6
7
8
9
Promise.all([
fetch(...).then(...),
fetch(...).then(...),
fetch(...).then(...),
]).then(
// succeeded
).catch(
// error handling (catches exceptions in the fetch)
)

1.3 历史车轮碾过

PC → Web → Web 2.0 (UGC) → AI (AGI)

  • “框架” 是驱动技术发展的原动力
  • 我们需要好的抽象来表达人类世界中的需求
    • 简单可靠,聚集大量行业开发者
    • 灵活通用,构造各种应用程序

单机 → 互联网 → 移动计算 → ???

  • 机遇和不确定
  • 风险和回报

2 高性能计算中的并发编程

2.1 CRAY-1 超级计算机

The world’s most expensive love-seat” (1976)

  • 138 MFLOPS @ 115kW (Apple M3: 4.1 TFLOPS @ 20W)

c11-2.1-1.webp

2.2 高性能计算

“A technology that harnesses the power of supercomputers or computer clusters to solve complex problems requiring massive computation.” (IBM)
源自数值密集型科学计算任务

  • 物理系统模拟
    • 天气预报、航天、制造、能源、制药、……
    • 大到宇宙小到量子,有模型就能模拟
  • 矿厂
  • AI: 新时代的高性能计算 (之后专门讲解)
  • HPC-China 100

物理世界具有 “空间局部性”

  • “模拟物理世界” 的系统具有 embarrassingly parallel 的特性

2.3 高性能计算中的并行编程

通常计算图容易静态切分 (机器-线程两级任务分解)

  • 生产者-消费者解决一切
    • MPI - “message passing libraries”, OpenMP - “multi-platform shared-memory parallel programming (C/C++ and Fortran)”
1
2
3
#pragma omp parallel num_threads(128)
for (int i = 0; i < 1024; i++) {
}

Challenges

  • 网络通信、功耗管理、稳定性和容错、软件和工具链

3 数据中心中的并发编程

3.1数据中心程序:特点

“A network of computing and storage resources that enable the delivery of shared applications and data.” (CISCO)

以海量分布式数据 (存储) 为中心

  • 实时的 “小数据处理”
    • 内容分发、用户认证、视频直播、弹幕……
  • 离线的 “大数据处理”
    • 内容索引、数据挖掘……

我们的生活离不开数据中心应用

  • AI、搜索、社交、支付、游戏……

3.2 数据中心里的并发编程

Challenge: 高可靠、低延迟的多副本分布式存储和计算

  • 数据保持一致 (Consistency)、服务时刻可用 (Availability)、容忍机器离线 (Partition tolerance) 不可兼得

高吞吐 (QPS) & 低延迟的事件处理

  • 处理事件可能需要读写持久存储或请求网络上的服务
    • 延迟不确定
  • 线程维护和上下文切换都会带来开销

假设有数千/数万个请求同时到达服务器……

  • “Denial of Service, DoS”
    • 全国的小爱音箱在小米汽车发布会上同步瘫痪

3.3 协程:操作系统 “不感知” 的上下文切换

和线程概念相同 (独立堆栈、共享内存)

  • 但 “一直执行”,直到 yield() 主动放弃处理器
    • yield() 是函数调用
      • 只需保存/恢复 non-volatile 的寄存器
      • (线程切换需要保存/恢复全部寄存器)
  • 但 sleep (I/O) 时,所有协程都 “卡住了”
  • 失去了并行
1
2
3
// 只可能是 1122 或 2211
void T1() { printf("1"); printf("1"); yield(); }
void T2() { printf("2"); printf("2"); yield(); }

3.4 Go 和 Goroutine

小孩子才做选择,多处理器并行和轻量级并发我全都要!

  • Goroutine: 概念上是线程,实现是线程和协程的混合体

Goroutine 实现

  • 每个 CPU 上有一个 Go Worker,运行协程
  • 协程执行 blocking API (sleep, read)
    • 偷偷调用 non-blocking 的版本
    • 成功 → 立即继续执行
    • 失败 → 立即 yield 到另一个需要 CPU 的 goroutine
      • 太巧妙了!完全不浪费!

3.5 Go 语言中的同步与通信

Do not communicate by sharing memory; instead, share memory by communicating. ——Effective Go

共享内存 = 万恶之源

  • 信号量/条件变量:实现了同步,但没有实现 “通信”
    • 数据传递完全靠手工 (没上锁就错了)

但 UNIX 时代就有一个实现并行的机制了

  • cat *.txt | wc -l
    • 管道是一个天然的生产者/消费者!
    • 为什么不用 “管道” 实现协程/线程间的同步 + 通信呢?
      • Channels in Go

“Leader/follower” 架构

  • 有一个集中的 “总控” 负责调度
  • 在可靠的消息机制上实现任务分派
    • Leader 串行处理所有请求
    • 例子:哲学家吃饭时,由服务员掌管叉子

你可能会觉得,管叉子的人是性能瓶颈?

  • 一大桌人吃饭,每个人都叫服务员的感觉
  • Premature optimization is the root of all evil (D. E. Knuth)

抛开 workload 谈优化就是耍流氓

  • 吃饭的时间通常远远大于请求服务员的时间
  • 如果一个 manager 搞不定,可以分多个 (fast/slow path)
    • The Google File System (SOSP’03) 开启大数据时代

4 人工智能时代的并发编程

4.1 大语言模型背后的计算

“Attention Is All You Need”

  • “Transformers”:类似人处理问题的思维方式(注意力机制)。

4.2 机器学习系统中的并发编程

Challenge: 既计算密集,又数据密集

  • 推理
    • GPT-3: 175B 参数 (~300GB VRAM, FP-16)
      • GPT-3 single training run cost: ~$5,000,000
    • LLaMA2: 70B 参数 (~65GB VRAM, FP-16)
      • 美国人断供芯片 = 三体人行为
  • 训练
    • 320TB 语料
    • 相比图片和视频,还是小弟弟
      解决:所有能想到的技术,全部都用上了!

对并发编程的争议一直存在,例如 “Threads cannot be implemented as a library”。实际上,我们在很多应用领域看到了 “领域特定” 的解决方案:Web 中的异步编程、高性能计算中的 MPI 和 OpenMI、数据中心中的 goroutines、人工智能时代的 CUDA。更有趣的是,我们可以看到:改变世界的技术,往往只是一个小小的奇思妙想, 最终坚持到底得到的——它们很难被 “规划” 出来,而是需要一些 “背道而驰” 的人坚持到底。在当今的社会环境下,我们最缺少的是有能力背道而驰的人——有了下一代人的进步,才会有包容他们的土壤。