
本文介绍如何在 Go 中异步启动长期运行的 Python 脚本,并实时、非阻塞地捕获其 stdout/stderr 输出,避免因 cmd.Run() 阻塞导致无法及时读取流数据。
本文介绍如何在 go 中异步启动长期运行的 python 脚本,并实时、非阻塞地捕获其 stdout/stderr 输出,避免因 `cmd.run()` 阻塞导致无法及时读取流数据。
在 Go 中调用外部 Python 程序时,若使用 cmd.Run(),程序会同步等待子进程退出,无法处理持续输出的日志或状态信息——这在运行无限循环(如游戏服务器、监控脚本)的 Python 程序时尤为致命。正确做法是分离进程启动与输出读取:通过 cmd.Start() 启动进程,再利用 StdoutPipe() 和 StderrPipe() 获取可读管道,并配合 goroutine + bufio.Scanner 实现并发、流式读取。
以下是完整、健壮的实现方案:
package main
import (
"bufio"
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("python", "game.py")
// 获取 stdout 和 stderr 的读取管道
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal("获取 stdout 管道失败:", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal("获取 stderr 管道失败:", err)
}
// 启动进程(不等待)
if err := cmd.Start(); err != nil {
log.Fatal("启动 Python 进程失败:", err)
}
// 并发读取 stdout 和 stderr
go copyOutput(stdout, "[STDOUT]")
go copyOutput(stderr, "[STDERR]")
// 主 goroutine 等待子进程结束(可选:也可用信号控制提前终止)
if err := cmd.Wait(); err != nil {
log.Printf("Python 进程异常退出: %v", err)
}
}
// copyOutput 从 io.Reader 流式读取并打印,带前缀标识来源
func copyOutput(r io.Reader, prefix string) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Printf("%s %s\n", prefix, scanner.Text())
}
if err := scanner.Err(); err != nil && err != io.EOF {
log.Printf("读取输出流时出错: %v", err)
}
}⚠️ 关键注意事项:
-
Python 端需显式刷新 stdout:默认情况下,Python 的
print在非 TTY 环境(如被 Go 子进程调用时)会启用行缓冲或全缓冲,导致输出延迟甚至卡住。务必在循环中调用sys.stdout.flush()(Python 2)或print(..., flush=True)(Python 3):立即学习“Python免费学习笔记(深入)”;
Secure Python Code Sandbox下载安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
# Python 3 示例(推荐) import time while True: print("Game tick:", time.time(), flush=True) time.sleep(1) 错误处理不可省略:
StdoutPipe()/StderrPipe()可能失败;cmd.Start()失败表示进程未启动成功;scanner.Err()应检查非io.EOF错误(如管道意外关闭)。资源安全:
cmd.Wait()是必需的,否则子进程可能成为僵尸进程;若需主动终止,可调用cmd.Process.Kill(),随后仍应调用cmd.Wait()清理。扩展建议:如需结构化日志、按行解析、或转发到 WebSocket/HTTP 接口,可在
copyOutput中替换fmt.Println为自定义处理器(如 JSON 编码、channel 发送等)。
该方案兼顾实时性、并发性与健壮性,是 Go 与长期运行外部进程交互的标准实践。

















