携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
如何用好一台计算机?
如何用一台 (可靠的) 计算机尽可能多地服务并行的请求
关键指标:QPS, tail latency, ...
我们有的工具
线程 (threads)
thread(start = true) {
println("${Thread.currentThread()} has run.")
}
协程 (coroutines)
多个可以保存/恢复的执行流 (M2 - libco)
比线程更轻量 (完全没有系统调用,也就没有操作系统状态)
数据中心:协程和线程
数据中心
同一时间有数千/数万个请求到达服务器
计算部分 需要利用好多处理器 线程 → 这就是我擅长的 (Mandelbrot Set)
协程 → 一人出力,他人摸鱼 I/O 部分 会在系统调用上 block (例如请求另一个服务或读磁盘) 协程 → 一人干等,他人围观(一个线程里只能运行一次协程) 线程 → 每个线程都占用可观的操作系统资源
Go 和 Goroutine Go: 多处理器并行和轻量级并发 Goroutine: 概念上是线程,实际是线程和协程的混合体 每个 CPU 上有一个 Go Worker,自由调度 goroutines 执行到 blocking API 时 (例如 sleep, read) Go Worker 偷偷改成 non-blocking 的版本 成功 → 立即继续执行
失败 → 立即 yield 到另一个需要 CPU 的 goroutine 太巧妙了!CPU 和操作系统全部用到 100%
现代编程语言上的系统编程
Do not communicate by sharing memory; instead, share memory by communicating. ——Effective Go
共享内存 = 万恶之源 在奇怪调度下发生的各种并发 bugs 条件变量:broadcast 性能低,不 broadcast 容易错 信号量:在管理多种资源时就没那么好用了
既然生产者-消费者能解决绝大部分问题,提供一个 API 不就好了? producer-consumer.go 缓存为 0 的 channel 可以用来同步 (先到者等待)
import "fmt"
var stream = make(chan int, 10)
const n = 4
func produce() {
for i := 0; ; i++ {
fmt.Println("produce", i)
stream <- i
}
}
func consume() {
for {
x := <- stream
fmt.Println("consume", x)
}
}
func main() {
for i := 0; i < n; i++ {
go produce()
}
consume()
}
身边的并发编程
Web 2.0 时代 (1999)
人与人之间联系更加紧密的互联网
“Users were encouraged to provide content, rather than just viewing it.”
你甚至可以找到一些 “Web 3.0”/Metaverse 的线索
是什么成就了今天的 Web 2.0?
浏览器中的并发编程:Ajax (Asynchronous JavaScript + XML)
HTML (DOM Tree) + CSS 代表了你能看见的一切
通过 JavaScript 可以改变它
通过 JavaScript 可以建立连接本地和服务器
人机交互程序:特点和主要挑战
特点:不太复杂
既没有太多计算
DOM Tree 也不至于太大 (大了人也看不过来)
DOM Tree 怎么画浏览器全帮我们搞定了
也没有太多 I/O
就是一些网络请求
单线程 + 事件模型
尽可能少但又足够的并发
一个线程、全局的事件队列、按序执行 (run-to-complete)
耗时的 API (Timer, Ajax, ...) 调用会立即返回
条件满足时向队列里增加一个事件
success: function(resp) {
$.ajax( { url: 'https://xxx.yyy.zzz/cart',
success: function(resp) {
// do something
},
error: function(req, status, err) { ... }
}
},
error: function(req, status, err) { ... }
);
总结 本次课回答的问题 Q: 什么样的任务是需要并行/并发的?它们应该如何实现?
Take-away message
并发编程的真实应用场景
高性能计算 (注重任务分解): 生产者-消费者 (MPI/OpenMP)
数据中心 (注重系统调用): 线程-协程 (Goroutine)
人机交互 (注重易用性): 事件-流图 (Promise)
编程工具的发展突飞猛进
自 Web 2.0 以来,开源社区改变了计算机科学的学习方式
每个人都要有一个 “主力现代编程语言”
Modern C++, Rust, Javascript, ...