更新页面 Python 并发语法
页面 kb/python-concurrency · 修改(rev 13) · · 2026-10-25 23:17
body
@@ -557,5 +557,4 @@ ```python - class Box: # 一个最小的「可以被 await 的东西」 def __init__(self): @@ -569,17 +568,17 @@ async def demo(): - print("1. 协程开始,跑到 await 之前") + print("1. 协程里:开始,跑到 await 之前") x = await box - print("3. 协程恢复了,x =", x) + print("3. 协程里:恢复了,x =", x) coro = demo() # 只建了协程对象,函数体一行都没执行 -print("0. 建好了协程对象:", type(coro).__name__) +print("0. 外面:建好了协程对象") y = coro.send(None) # 跑到 yield 那里停下,yield 出来的东西就是 send 的返回值 -print("2. send 返回了:", "box" if y is box else y) +print("2. 外面:send 返回了", "box" if y is box else y) box.done, box.value = True, 42 # 相当于 set_result(42) try: coro.send(None) # 从 yield 下一行接着跑 except StopIteration: - print("4. 协程跑完了,第二次 send 抛 StopIteration") + print("4. 外面:第二次 send 抛了 StopIteration,协程跑完了") ``` @@ -587,18 +586,22 @@ ``` -0. 建好了协程对象: coroutine -1. 协程开始,跑到 await 之前 -2. send 返回了: box -3. 协程恢复了,x = 42 -4. 协程跑完了,第二次 send 抛 StopIteration -``` - -逐行看: - -- 调 `demo()` 不执行函数体,只返回一个协程对象。第 0 行打印时,第 1 行还没打印。 -- 第一次 `send(None)` 从函数开头跑起,打印第 1 行,然后遇到 `await box`,进到 `box.__await__()` 里,执行到 `yield self` 停下。协程的局部变量和执行位置都保存在协程对象里,`yield` 出来的 `box` 成了 `send` 的返回值,所以第 2 行打印出 box。 -- 第二次 `send(None)` 从 `yield` 的下一行接着跑。这时 `box.done` 已经是 True,`__await__` 执行 `return self.value`,这个返回值就是 `await box` 整个表达式的值,所以 `x = 42`。函数跑完,`send` 抛 `StopIteration`。 - -协程自己没法恢复自己,谁调 `send`,谁就决定它什么时候接着跑。事件循环要做的,就是在合适的时候替它调 `send`。 +0. 外面:建好了协程对象 +1. 协程里:开始,跑到 await 之前 +2. 外面:send 返回了 box +3. 协程里:恢复了,x = 42 +4. 外面:第二次 send 抛了 StopIteration,协程跑完了 +``` + +1 和 3 是协程 `demo` 里面打印的,0、2、4 是协程外面那几行打印的。按执行的先后,控制权在外面和协程里面之间来回切换: + +| 顺序 | 外面的代码 | 协程 `demo` 里面 | +| --- | --- | --- | +| 1 | `coro = demo()` 只建了协程对象,然后打印 0 | 还没开始,一行都没执行 | +| 2 | 调 `coro.send(None)`,把控制权交给协程 | 从头开始跑,打印 1,执行到 `await box`,进到 `box.__await__` 里,在 `yield self` 处停下。局部变量和执行位置都存在协程对象里 | +| 3 | `send` 返回了 `box`,就是 `yield` 出来的那个值,打印 2。然后把 `box.done` 设成 True、`box.value` 设成 42 | 停着不动 | +| 4 | 再调一次 `coro.send(None)` | 从 `yield` 的下一行接着跑:`return self.value` 让 `await box` 的值是 42,所以 `x = 42`,打印 3,函数结束 | +| 5 | 这次 `send` 抛出 `StopIteration`,外面接住它,打印 4 | 已经结束 | + +协程自己没法恢复自己,谁调 `send`,谁就决定它什么时候接着跑。事件循环做的,就是表里第 2 步和第 4 步「调 `send`」的那个角色。 #### 一个最小的事件循环