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 进程崩溃时自动重启,调用方无感知: