# gobridge 在 Go 与 Python 之间建立双向通信桥接,将 Go 的 `channel` 与 Python 的 `yield` 原生对接,支持普通调用、单向流、双向流。 底层通过 **Unix Domain Socket (UDS)** 通信,Go 侧维护 **Worker 进程池**,Python 侧以多线程方式并发处理请求。 ## 特性 - **零配置序列化**:Go struct/slice ↔ Python dict/list 通过 JSON 自动互转 - **原生流语义**:Go `chan T` 对应 Python `Iterator[T]`,无需额外 API - **进程池**:Go 自动启动并管理多个 Python 子进程,崩溃后自动重启 - **ctx 取消**:Go `context` 取消时自动中断 Python 计算,无需函数内检查 - **四种调用模式**:普通、流式输出、流式输入、双向流,同一个 `Invoke` 函数自动推断 ## 安装 ```bash go get git.fsdpf.net/go/gobridge ``` Python 端直接复制 `python/gobridge/` 目录到项目中,无需安装依赖(仅用标准库)。 ## 快速开始 **Python 端(worker.py):** ```python from gobridge import gobridge, run from typing import Iterator @gobridge def add(a: int, b: int) -> int: return a + b run() ``` **Go 端:** ```go pool, _ := gobridge.NewPool("worker.py") defer pool.Close() ctx := context.Background() sum, _ := gobridge.Invoke[int](ctx, pool, "add", 3, 4) fmt.Println(sum) // 7 ``` ## 四种调用模式 ### 1. 普通调用 ```go // Go sum, err := gobridge.Invoke[int](ctx, pool, "add", 3, 4) user, err := gobridge.Invoke[User](ctx, pool, "get_user", 42) result, err := gobridge.Invoke[[]User](ctx, pool, "enrich_users", users) ``` ```python # Python @gobridge def add(a: int, b: int) -> int: return a + b @gobridge def get_user(uid: int) -> dict: return {"id": uid, "name": f"user_{uid}", "score": uid * 1.5} @gobridge def enrich_users(users: list) -> list: for u in users: u["level"] = "gold" if u["score"] >= 10 else "silver" return users ``` ### 2. 流式输出(Python yield → Go channel) 返回类型为 `chan T` 时自动进入流式输出模式,Python 函数使用 `yield`,Go 侧通过 `range` 消费。 ```go // Go ch, err := gobridge.Invoke[chan int](ctx, pool, "range_gen", 1, 6) for v := range ch { fmt.Println(v) // 1 2 3 4 5 } userCh, err := gobridge.Invoke[chan User](ctx, pool, "gen_users", 3) for u := range userCh { fmt.Println(u) } ``` ```python # Python @gobridge def range_gen(start: int, stop: int) -> Iterator[int]: for i in range(start, stop): yield i @gobridge def gen_users(count: int) -> Iterator[dict]: for i in range(1, count + 1): yield {"id": i, "name": f"user_{i}", "score": float(i * 3)} ``` ### 3. 流式输入(Go channel → Python Iterator) 参数中含 `chan T` 且返回非 `chan` 时自动进入流式输入模式。 ```go // Go inputCh := make(chan int, 10) go func() { for i := 1; i <= 5; i++ { inputCh <- i } close(inputCh) }() total, err := gobridge.Invoke[int](ctx, pool, "sum_stream", inputCh) fmt.Println(total) // 15 ``` ```python # Python @gobridge def sum_stream(numbers: Iterator[int]) -> int: return sum(numbers) ``` ### 4. 双向流(Go channel 输入 + Go channel 输出) 参数含 `chan T` 且返回类型也为 `chan R` 时自动进入双向流模式。 ```go // Go inCh := make(chan User, 5) go func() { for _, u := range users { inCh <- u } close(inCh) }() outCh, err := gobridge.Invoke[chan User](ctx, pool, "process_users", inCh) for u := range outCh { fmt.Println(u) } ``` ```python # Python @gobridge def process_users(users: Iterator[dict]) -> Iterator[dict]: for u in users: yield {"id": u["id"], "name": u["name"].upper(), "score": u["score"] * 2} ``` ## ctx 取消 Go 的 `context` 取消会自动中断 Python 侧的执行,无需在 Python 函数中做任何检查: ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() // 超时或手动 cancel() 后,Python 计算立即中断,返回 context.DeadlineExceeded result, err := gobridge.Invoke[int](ctx, pool, "slow_compute", 1000000) ``` ```python @gobridge def slow_compute(n: int) -> int: total = 0 for i in range(n): total += i # ctx 取消时此处自动抛出 InterruptedError,无需手动检查 return total ``` **实现机制:** 1. Go ctx 取消 → 发送 `cancel` 消息给 Python 2. Python 单 reader 线程收到 `cancel` → 向执行线程注入 `InterruptedError`(`PyThreadState_SetAsyncExc`) 3. Python 函数在下一条字节码指令处中断 4. Go 同时关闭连接,解除阻塞的读写操作 > 限制:长时间不释放 GIL 的 C 扩展(如大规模 numpy 矩阵运算)无法被中断,需等其释放 GIL 后才触发。 ## 进程自动重启 Python worker 进程崩溃时自动重启,调用方无感知: ``` Python 进程崩溃 → monitor goroutine 检测到退出 → 排空失效连接 → 指数退避重启(100ms → 200ms → ... → 30s) → 新进程就绪后恢复连接池 ``` ## 配置 `NewPool` 使用函数选项模式,第一个参数为脚本路径: ```go // 最简调用 pool, err := gobridge.NewPool("worker.py") // 完整配置 pool, err := gobridge.NewPool("worker.py", gobridge.WithWorkers(4), // Python 进程数量,默认 2 gobridge.WithMaxConns(8), // 每进程最大连接数,默认 4 gobridge.WithPythonExe("python3"), // 可执行文件,默认 "python3" gobridge.WithWorkDir("/path/to/workdir"), // 工作目录,默认继承当前进程 gobridge.WithEnv("PYTHONUNBUFFERED=1", "K=V"), // 附加环境变量,与当前进程环境合并 gobridge.WithSocketDir("/var/run/myapp"), // socket 文件目录,默认 /tmp gobridge.WithStdout(os.Stdout), // 子进程 stdout,默认 os.Stdout gobridge.WithStderr(os.Stderr), // 子进程 stderr,默认 os.Stderr ) // 静默模式:丢弃子进程输出 pool, err := gobridge.NewPool("worker.py", gobridge.WithStdout(io.Discard), gobridge.WithStderr(io.Discard), ) ``` | Option | 说明 | 默认值 | |--------|------|--------| | `WithWorkers(n)` | Python 进程数量 | `2` | | `WithMaxConns(n)` | 每进程最大连接数 | `4` | | `WithPythonExe(exe)` | 可执行文件 | `"python3"` | | `WithScriptArgs(args...)` | 脚本路径之后的附加参数 | 无 | | `WithWorkDir(dir)` | 子进程工作目录 | 继承当前进程 | | `WithEnv(kv...)` | 附加环境变量 `"K=V"` | 无 | | `WithSocketDir(dir)` | UDS socket 文件目录 | `"/tmp"` | | `WithStdout(w)` | 子进程标准输出 | `os.Stdout` | | `WithStderr(w)` | 子进程标准错误 | `os.Stderr` | ## 使用 uv 管理 Python 环境 推荐使用 [uv](https://github.com/astral-sh/uv) 管理 Python 版本和虚拟环境。 **方式一:`uv run`(推荐,无需手动激活环境)** ```go // 等价于执行:uv run worker.py pool, err := gobridge.NewPool("run", gobridge.WithPythonExe("uv"), gobridge.WithScriptArgs("worker.py"), gobridge.WithWorkDir("./worker"), // uv 项目目录(含 pyproject.toml) ) ``` **方式二:直接使用虚拟环境的 python** ```bash cd worker && uv sync ``` ```go venvPython, _ := exec.LookPath("worker/.venv/bin/python") pool, err := gobridge.NewPool("worker/worker.py", gobridge.WithPythonExe(venvPython), ) ``` **方式三:shell 脚本封装(适合 CI/部署)** ```bash #!/bin/sh # run_worker.sh cd "$(dirname "$0")" exec uv run python worker.py ``` ```go pool, err := gobridge.NewPool("./run_worker.sh", gobridge.WithPythonExe("/bin/sh"), ) ``` **典型项目结构:** ``` myproject/ ├── main.go ├── go.mod └── worker/ ├── pyproject.toml ├── uv.lock ├── .venv/ └── worker.py ``` `pyproject.toml`: ```toml [project] name = "worker" version = "0.1.0" requires-python = ">=3.11" dependencies = [] [tool.uv.sources] gobridge = { path = "../../python", editable = true } ``` ## 通信协议 ### 整体架构 ``` Go 进程 ┌─────────────────────────────────────────────────────────┐ │ Invoke[R](ctx, pool, method, args...) │ │ │ │ │ ┌────▼─────────────────────────────────────────────┐ │ │ │ Pool │ │ │ │ workers[0] workers[1] ... workers[N-1] │ │ │ │ (轮询选择) │ │ │ └────┬─────────────────────────────────────────────┘ │ │ │ 每 worker 维护 M 个可复用连接 │ └───────┼─────────────────────────────────────────────────┘ │ Unix Domain Socket(每 worker 独立 .sock 文件) ┌───────▼──────────────┐ ┌──────────────────────────┐ │ Python 进程 0 │ │ Python 进程 1 │ │ worker.py │ │ worker.py │ └──────────────────────┘ └──────────────────────────┘ ``` ### Python Worker 内部结构 ``` Python 进程 ┌──────────────────────────────────────────────────────────────┐ │ run() ── UDS server.accept() 循环 │ │ │ │ │ 每个连接 → 独立线程 _handle_conn() │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ _handle_conn(连接线程) │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ │ │ _ConnMux(单 reader 线程) │ │ │ │ │ │ │ │ │ │ │ │ socket ──► 读消息 │ │ │ │ │ │ │ │ │ │ │ │ │ ┌────────┼──────────┐ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ │ │ call_q chunk_q cancel │ │ │ │ │ └─────────┬────────┬──────────┼───────────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ ▼ │ │ │ │ 主循环读取 │ PyThreadState_SetAsyncExc │ │ │ │ _dispatch() │ → 执行线程抛 InterruptedError│ │ │ │ │ │ │ │ │ │ ┌──────▼──────┐ │ │ │ │ │ │ @gobridge fn│ │ │ │ │ │ │ │ │ │ │ │ │ │ 普通函数 │ │ │ │ │ │ │ return val ──────────────► result/error │ │ │ │ │ │ │ │ │ │ │ │ 生成器函数 │ │ │ │ │ │ │ yield val ───────────────► chunk × N │ │ │ │ │ │ │ + end │ │ │ │ │ 流式输入 │ │ │ │ │ │ │ Iterator ◄─┘ │ ← chunk_q │ │ │ │ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ ``` **消息帧:** `[4字节大端长度][JSON载荷]` **消息类型:** | type | 方向 | 含义 | |----------|--------------|------------------------------| | `call` | Go → Python | 调用请求 | | `result` | Python → Go | 普通返回值 | | `chunk` | 双向 | 流数据块 | | `end` | 双向 | 流结束标记 | | `error` | 双向 | 错误响应 | | `cancel` | Go → Python | 取消请求,触发 InterruptedError | ## 项目结构 ``` gobridge/ ├── protocol.go # Message 结构与类型常量 ├── framing.go # 帧读写(4字节长度前缀 + JSON) ├── worker.go # Python 子进程管理 + UDS 连接池 + 自动重启 ├── pool.go # 多进程池(轮询负载均衡)+ Option 函数 ├── client.go # Invoke[R] 泛型函数(四种模式自动推断) ├── example/ │ ├── main.go # 完整调用示例 │ └── worker.py # Python 函数示例 └── python/ └── gobridge/ └── __init__.py # Python 库(expose、run、_ConnMux) ``` ## 类型对应关系 | Go 类型 | Python 类型 | |-----------|---------------| | `int` | `int` | | `float64` | `float` | | `string` | `str` | | `bool` | `bool` | | `struct` | `dict` | | `[]T` | `list` | | `chan T` | `Iterator[T]` | ## 参考 本项目的进程池、UDS 通信、帧协议设计参考自 [pyproc](https://github.com/YuminosukeSato/pyproc),在此基础上增加了 Go channel 与 Python yield 的流式对接及 ctx 取消支持。