R

ReflexKR

Reflex 官方文档

深夜厨房里的并发

·

课程概览

1

第一晚的长队:并发与并行

2

多招几个厨师:threading 基础

3

黑板上的数字错了:竞争条件与 Lock

4

只有三个灶台:同步工具

5

刀与砧板:死锁

6

订单夹:queue

7

外派厨师:concurrent.futures

8

面团为什么快不起来:GIL 与并行

9

一个人跑全场的前厅经理:asyncio 入门

10

团体预订取消了:结构化并发

11

前厅停住了:阻塞调用与混合使用

12

评论家之夜:综合

深夜厨房里的并发
第1课

时间 0:00–0:50

第一晚的长队:并发与并行

今晚的订单

#01 · 第一晚

第一晚的长队

危机

一开门就涌进来八桌客人。副厨一个人,一单一单地做。汤在锅里煮着,人就干站在旁边等。

目标

1 秒内送出八份订单。

深夜食堂 同时

开业第一晚,十点整。门一开,客人就涌了进来。你把第一单的汤放上火,然后守在锅前,一直等到汤开。三分钟后端出第一碗,再开始做第二单。

尹瑞妍主厨看着计时器说:“汤煮着的时候,你的手在干什么?什么也没干。这就是今晚的问题。”

1-1. 一单 0.3 秒,八单要多久?(6 分钟)

我们用 Python 模拟一下“等汤煮开”这件事。time.sleep(0.3) 的意思是“什么都不做,等 0.3 秒”。就当是把三分钟缩成了 0.3 秒。

import time

def make_order(n):
    time.sleep(0.3)   # 等汤煮开
    return f"#{n}"

start = time.perf_counter()
done = [make_order(n) for n in range(1, 9)]
print(f"{time.perf_counter() - start:.2f}s", done)
# 2.40s ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8']
  • time.perf_counter() 是专门用来计时的时钟。读两次再相减,就得到中间经过了多少秒。
  • 八个 0.3 秒的订单一个一个做,就是 0.3 × 8 = 2.4 秒。这 2.4 秒里,副厨真正动手的时间几乎为零,全都是在等。

每次运行,最后几位数字可能略有不同。计时类示例的结果永远只是“大约”。

1-2. 要等的活和手不停的活(8 分钟)

厨房里的活分两种。

要等的活(I/O)手不停的活(CPU)
在厨房里煮汤、烤炉烘烤、等送货揉面、切丝
在程序里网络响应、读文件、数据库计算、压缩、图像处理
等的时候人闲着人一直在忙

并发能大幅节省时间,靠的是要等的活多。汤在煮的时候,再架上一口锅就行了。手不停的活必须真的多几个人才能变快,而 Python 在这里藏着一条规矩。这个留到第 8 课再说。

1-3. 并发和并行不是一回事(8 分钟)

这两个词经常混着用,但意思不同。

  • 并发(concurrency):多件事轮流推进,看上去像是一起往前走。一个厨师把汤架上火,趁这空当去搅另一口锅。
  • 并行(parallelism):多件事真正在同一时刻执行。四个厨师各自同时揉面团。

并行是实现并发的一种方式。如果厨房里大多是要等的活,哪怕只有一双手,光靠并发也能快很多。在下面的时间线里,轮流切换“一个人按顺序”和“多个厨师”跑一跑。一根横条就是一份订单,横轴是时间。

厨房时间线

订单

6

厨师

3

烹饪

等待

已取消

失败

8 份订单,每份 0.3 秒。一个人按顺序做,横条排成长长的一行;厨师一多,横条就分成几行叠在一起,变短了。
  • “一个人按顺序”时,八根横条首尾相接排在一行,大约要 2.4 秒。
  • “多个厨师”时,厨师设成 3 个,三行叠在一起,大约 0.9 秒;设成 8 个,大约 0.3 秒。
  • 这个实验台不是动画。服务器上真的有线程在执行 time.sleep,图上画的是每份订单实际开始和结束的时刻。

1-4. 多加厨师能快多少?(5 分钟)

如果订单长度都一样,总耗时就是“分到订单最多的那个厨师的订单数 × 一份订单的时间”。

import math

def estimate(orders, cooks, each=0.3):
    rounds = math.ceil(orders / cooks)   # 一个厨师最多分到几份订单
    return round(rounds * each, 2)

for cooks in (1, 3, 4, 8, 16):
    print(cooks, estimate(8, cooks))
# 1 2.4
# 3 0.9
# 4 0.6
# 8 0.3
# 16 0.3
  • 3 个厨师时,8 ÷ 3 向上取整是 3 轮,所以是 0.9 秒。和上面时间线里看到的数字一样。
  • 厨师比订单还多,就不会再快了。一份订单的时间(0.3 秒)就是底线。
  • 订单长短不一时,最长的那份就是底线。活分得再细,也不可能比最长的那一块更早做完。

1-5. 先睹为快:三种工具(8 分钟)

Python 里有三种同时做多件事的工具。今天只尝个味道,从下一课开始一个一个深入。

import threading

threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
    th.start()        # 八个厨师同时开工
for th in threads:
    th.join()         # 等所有人做完
# 约 0.30 秒
import asyncio

async def make_order_async(n):
    await asyncio.sleep(0.3)   # 等的时候去看别的订单
    return f"#{n}"

async def main():
    return await asyncio.gather(*(make_order_async(n) for n in range(1, 9)))

print(asyncio.run(main()))
# ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8']  (约 0.31 秒)
工具换成厨房来说适合的活课次
threading同一间厨房里多请几个厨师要等的活、老代码2~7
multiprocessing连冰箱都分开用的分店厨房手不停的活(CPU)8
asyncio一个人跑几十张桌子的前厅经理要等的活特别多的时候9~11

主厨的提醒

多请厨师确实会变快。可厨房也会变吵。两个人同时往一块黑板上写数字,会怎么样?第 3 课你就亲眼看到了。

1-6. 并发不是免费的(5 分钟)

多请厨师是有代价的。

  • 管理成本:创建线程、切换执行顺序都要花时间。订单特别短的话,反而可能更慢。
  • 新的 bug:两个人同时动同一份食材,结果就会出错(第 3 课)。互相等着对方手里的工具,厨房就会停摆(第 5 课)。
  • 思考成本:“代码一行一行按顺序执行”这个前提被打破了。所以这门课每次都要用时间线亲眼确认“什么时候执行了什么”。

打烊(10 分钟)

今晚的目标是 1 秒内送出八份订单。订单全是“要等的活”,那就让八个厨师一人负责一份。

start = time.perf_counter()
threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
    th.start()
for th in threads:
    th.join()
print(f"{time.perf_counter() - start:.2f}s")
# 0.30s

主厨按停计时器。“从 2.4 秒到 0.3 秒。第一天来说,还不赖。可厨师变成八个以后,谁做了几份,由谁来数?”

营业日志 — 第一晚。8 份订单全是要等的活,由 8 个厨师分头负责,0.3 秒做完。明天正式招厨师。

课后练习

  1. 在时间线里把订单设为 12 份、厨师设为 4 个。先算一算会花多少时间,再运行看看。
  2. 把 make_order 里的 time.sleep(0.3) 换成一个累加数字的循环(手不停的活),再用线程跑,测一测有没有变快。如果结果很奇怪,第 8 课会告诉你原因。
  3. 从你的日常生活里各写出三件“要等的活”和“手不停的活”。

主厨的配方卡

  • 要等的活(I/O)靠并发能大幅加速。手不停的活(CPU)需要真正的并行。

  • 并发 = 轮流推进,并行 = 同一时刻执行。

  • 计时:start = time.perf_counter() … time.perf_counter() - start

  • 三种工具:threading(多个厨师)、multiprocessing(多间厨房)、asyncio(一个人跑的前厅)。

  • 并发不是免费的:管理成本和新的 bug 会随之而来。

← 上一课

课程概览

下一课 →

第2课 · 多招几个厨师:threading 基础

订阅新课时和新课程通知

本课程新增课时或推出后续课程时,我们会发邮件通知你。

收集项目:电子邮箱 · 用途:本课程的新课时和上线通知 · 保存期限:直到你要求删除。不会用于其他用途,也不会提供给他人。回复任意通知邮件,我们会立即删除。

问答

关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。

以纯文本显示,不支持链接和格式。

R

ReflexKR

动手学 Python 和 Reflex。所有示例均已在 Reflex 0.10.0 上实际运行验证。

ReflexKR 免费,没有横幅广告。如果对你有帮助,欢迎请我们喝杯咖啡。 ☕ 请我们喝杯咖啡

联盟链接与赞助说明
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex