From 30004e44eece48cdaf9f779a0e05b86fd847d716 Mon Sep 17 00:00:00 2001 From: what Date: Tue, 14 Apr 2026 16:49:12 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20handler=20?= =?UTF-8?q?=E4=B8=AD=E4=BD=BF=E7=94=A8=20threading.Thread=20=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 63152d2..8403588 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,50 @@ def slow_compute(n: int) -> int: > 限制:长时间不释放 GIL 的 C 扩展(如大规模 numpy 矩阵运算)无法被中断,需等其释放 GIL 后才触发。 +## 注意事项 + +### 在 handler 中使用 `threading.Thread` + +handler 函数内部可以启动后台线程,但行为取决于是否等待: + +```python +# ✅ fire-and-forget:立即返回,连接立即释放,后台线程独立运行 +@expose +def do_something(): + threading.Thread(target=long_task, daemon=True).start() + return "ok" + +# ⚠️ 等待线程:连接被占用直到线程结束,等同于直接在 handler 里执行 +@expose +def do_something(): + t = threading.Thread(target=long_task) + t.start() + t.join() # 连接在此阻塞 + return "ok" +``` + +**`call_go()` 只能在 handler 的原始线程中调用。** `call_go()` 依赖线程局部变量 `_local.mux` 获取当前连接,后台线程中该变量不存在,调用会抛出 `RuntimeError`: + +```python +@expose +def do_something(): + def bg(): + call_go("Method") # ❌ RuntimeError: call_go() must be called within a gobridge handler + threading.Thread(target=bg, daemon=True).start() + return "ok" +``` + +如果后台线程的结果需要回调 Go,应在 handler 线程中通过 `queue.Queue` 等待后台线程结果后再调用: + +```python +@expose +def do_something(): + result_q = queue.Queue() + threading.Thread(target=lambda: result_q.put(compute()), daemon=True).start() + result = result_q.get() # 等待后台线程 + return call_go[str]("Process", result) # ✅ 在 handler 线程中调用 +``` + ## 进程自动重启 Python worker 进程崩溃时自动重启,调用方无感知: