普贤教程 › 进阶教程
并发与 Actor
并发只在编译轨可用。解释器是单线程子集,遇到 spawn / chan 直接报错——这不是缺陷,是取舍:解释器只负责"跑通逻辑",并发交给编译产物。
spawn 与 chan
px
def work(ch, n): var s = 0 var i = 0 while i < n: s += i i += 1 ch.send (s) def main(): var ch = chan [int](4) spawn work(ch, 100) spawn work(ch, 200) print(str(ch.recv())) print(str(ch.recv()))
sh
px build conc.px && ./build/conc
text
4950 19900
| 原语 | 作用 |
|---|---|
spawn f(args) | 启动一个并发执行单元 |
chan [T](n) | 带类型与缓冲容量的通道 |
ch.send (v) | 发送(缓冲满则阻塞) |
ch.recv () | 接收(无数据则阻塞) |
注意空格
ch.send (v) 与 ch.recv () 的括号前有一个空格。这是语言当前的写法约定,别省。用 chan 收集结果
px
def fetch_all(urls): var ch = chan [dict](len(urls)) for u in urls: spawn fetch_one(u, ch) var out = [] var i = 0 while i < len(urls): out.append(ch.recv()) i += 1 return out def fetch_one(url, ch): var r = http_request(url, "GET", null) ch.send ({"url": url, "status": r["status"]})
要点:开启多少个并发单元,就 recv 多少次。少收会泄漏,多收会死等。
worker 池
任务数远大于并发上限时,用固定数量的 worker 从同一个通道取任务:
px
def worker(id, jobs, results): while true: var job = jobs.recv () if job == null: results.send ({"id": id, "done": true}) return var v = job * 2 results.send ({"id": id, "value": v}) def main(): var jobs = chan [int](16) var results = chan [dict](16) spawn worker(1, jobs, results) spawn worker(2, jobs, results) for i in range(1, 7): jobs.send (i) jobs.send (null) # 每个 worker 一个结束哨兵 jobs.send (null) var got = 0 while got < 8: var r = results.recv () got += 1 print("done")
actor:把状态关进一个邮箱
共享状态最容易出事。actor 的思路是:状态只由持有它的那个执行单元修改,外部通过消息请求它做事,天然免锁。
sh
pxpkg add actor@0.2.0 && pxpkg install
px
import actor def main(): var a = ac_new({ "inc": fn(state, body): state["n"] = state["n"] + body return state["n"], "get": fn(state, body): state["n"] }, {"n": 0}) ac_call(a, "inc", 5) ac_call(a, "inc", 3) print(str(ac_call(a, "get", null).ok())) ac_stop(a)
text
8处理函数的两种写法只有一条表达式时用
fn(state, body): 表达式;要改 state 就必须写成多行块,末尾 return,并且这个 fn 在字典里后面要跟一个逗号。fn(state, body): state["n"] = ... 这种把赋值当表达式写会直接语法错误。| 函数 | 行为 |
|---|---|
ac_new(handlers, init) | 创建 actor,handlers 是「消息名 → fn(state, body)」 |
ac_call(a, name, body) | 发消息并等应答,返回 Ok(result);未注册的消息返回 Err |
ac_send(a, name, body) | 只发不等 |
ac_stop(a) | 优雅停止,已排队消息会先处理完 |
并发编程的四条纪律
- 不要跨执行单元改同一个列表/字典。要共享就通过通道传消息。
- 每个 spawn 都要有人等。通道没人收,发送方会永久阻塞。
- 退出要有哨兵。用
null或特定消息告诉 worker 结束,别靠"自然结束"猜。 - 并发单元里的错误要回传。子单元崩了主单元不知道,现象是"卡住不动"——把 err 信息也 send 回来。
什么时候不要并发
| 场景 | 建议 |
|---|---|
| CPU 密集且能拆块 | 并发有效(编译轨) |
| IO 等待(HTTP、数据库) | 并发有效 |
| 需要强一致的计数 | 用数据库原子操作,不要内存计数 |
| 逻辑简单、QPS 低 | 串行更省心,别引入并发 |
解释器不能跑并发
px run 遇到 spawn / chan 会报错。开发期要么只测串行逻辑,要么始终用 px build + 跑产物来验证。