讲师中心 微信公众号
AI工具推荐 视频效率加速

Go语言实现与子进程的实时交互:通过标准输入输出进行通信

陌丽君_6530

陌丽君_6530

发布时间:2025-12-02 16:12:25

|

807人浏览过

|

来源于php中文网

原创

Go语言实现与子进程的实时交互:通过标准输入输出进行通信

本文详细介绍了如何在go语言中启动一个外部子进程(如python脚本),并通过标准输入(stdin)和标准输出(stdout)实现实时的双向通信。教程涵盖了使用`os/exec`包创建命令、建立输入输出管道、启动进程、进行循环读写操作以及正确处理错误和等待进程结束的关键步骤,旨在帮助开发者构建高效的进程间通信解决方案。

在现代软件开发中,Go程序经常需要与其他语言编写的工具或服务进行交互。其中一种常见的模式是通过启动子进程,并利用其标准输入(stdin)和标准输出(stdout)进行实时数据交换。本教程将以一个Go程序与Python脚本通信的例子,详细讲解如何实现这一机制。

子进程准备:Python示例脚本

首先,我们需要一个能够接收标准输入并返回结果的子进程。这里我们使用一个简单的Python脚本 add.py,它会持续读取一行输入,将其作为表达式求值,并将结果写入标准输出。

# add.py
import sys

while True:
    try:
        # 读取一行输入,移除末尾换行符,并进行求值
        line = sys.stdin.readline().strip()
        if not line: # 如果读取到空行,可能是输入结束,退出循环
            break
        result = eval(line)
        sys.stdout.write('%s\n' % result)
        sys.stdout.flush() # 立即刷新缓冲区,确保Go程序能及时读取
    except Exception as e:
        sys.stderr.write(f"Error: {e}\n")
        sys.stderr.flush()
        break # 发生错误时退出

脚本说明:

  • sys.stdin.readline():从标准输入读取一行。
  • eval():将字符串作为Python表达式求值。
  • sys.stdout.write('%s\n' % result):将结果写入标准输出,并添加换行符。
  • sys.stdout.flush():这一步非常关键,它确保Python的输出缓冲区被立即清空,这样Go程序才能实时读取到数据,而不是等待缓冲区满或程序结束。

Go语言实现子进程通信

接下来,我们将编写Go程序来启动 add.py 脚本,并与其进行交互。

立即学习“go语言免费学习笔记(深入)”;

1. 初始化命令与管道

使用os/exec包来创建命令对象,并建立与子进程标准输入输出对应的管道。

package main

import (
    "bufio"
    "fmt"
    "log"
    "os/exec"
)

func main() {
    // 创建命令对象。-u 标志对于Python是重要的,它强制标准输出和标准错误不进行缓冲。
    c := exec.Command("python", "-u", "add.py")

    // 获取子进程的标准输入管道
    si, err := c.StdinPipe()
    if err != nil {
        log.Fatal("获取StdinPipe失败:", err)
    }

    // 获取子进程的标准输出管道
    so, err := c.StdoutPipe()
    if err != nil {
        log.Fatal("获取StdoutPipe失败:", err)
    }

    // 使用bufio.NewReader包装StdoutPipe,以便按行读取
    reader := bufio.NewReader(so)

    // 启动子进程
    err = c.Start()
    if err != nil {
        log.Fatal("启动子进程失败:", err)
    }

    // ... 后续读写操作
}

关键点:

  • exec.Command("python", "-u", "add.py"): 创建一个执行Python脚本的命令。-u 标志(unbuffered)对于Python进程的实时交互至关重要,它能确保标准输出和标准错误流不被缓冲,使得Go程序能立即接收到Python的输出。
  • c.StdinPipe() 和 c.StdoutPipe(): 这两个函数返回 io.WriteCloser 和 io.ReadCloser 接口,分别代表了Go程序向子进程的stdin写入数据,以及从子进程的stdout读取数据。
  • bufio.NewReader(so): 为了方便地按行读取子进程的输出,我们通常会用 bufio.NewReader 来包装 StdoutPipe。

2. 数据读写循环

一旦子进程启动,我们就可以在一个循环中向其写入数据,并读取其响应。

macOS UI 自动化技能包 —— 用 Python 控制你的 Mac
macOS UI 自动化技能包 —— 用 Python 控制你的 Mac

让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限

下载
// ... (接上文代码)

    // 现在进行一些数学运算,与子进程交互10次
    for i := 0; i < 10; i++ {
        // 构造要发送的表达式,并添加换行符
        expr := fmt.Sprintf("2+%d\n", i)

        // 写入数据到子进程的stdin
        _, err = si.Write([]byte(expr))
        if err != nil {
            log.Fatal("写入stdin失败:", err)
        }

        // 从子进程的stdout读取一行响应
        answer, err := reader.ReadString('\n')
        if err != nil {
            log.Fatal("读取stdout失败:", err)
        }

        // 打印结果
        fmt.Printf("对表达式 %q 的答案是 %q\n", expr, answer)
    }

    // ... (后续关闭和等待)

关键点:

  • si.Write([]byte(expr)): 将字符串转换为字节切片后写入 StdinPipe。务必在每条命令后加上 \n,因为Python脚本是按行读取的。
  • reader.ReadString('\n'): 从 bufio.Reader 中读取直到遇到换行符为止。这确保了我们能完整地获取子进程的一行输出。

3. 关闭管道与等待进程结束

完成所有交互后,必须关闭管道并等待子进程退出,以释放资源并避免僵尸进程。

// ... (接上文代码)

    // 关闭输入管道,通知子进程不再有更多输入
    // 这会导致Python脚本的sys.stdin.readline()返回空字符串,从而退出循环
    err = si.Close()
    if err != nil {
        log.Println("关闭StdinPipe失败:", err) // 记录错误但继续
    }

    // 关闭输出管道(可选,但良好实践)
    err = so.Close()
    if err != nil {
        log.Println("关闭StdoutPipe失败:", err) // 记录错误但继续
    }

    // 等待子进程退出并获取其退出状态
    err = c.Wait()
    if err != nil {
        log.Fatal("等待子进程退出失败:", err)
    }

    fmt.Println("子进程已成功退出。")
}

关键点:

  • si.Close(): 关闭 StdinPipe 会向子进程发送一个EOF(文件结束)信号。Python脚本中的 sys.stdin.readline() 在接收到EOF时会返回空字符串,从而允许脚本优雅地退出其 while True 循环。
  • so.Close(): 虽然不是强制性的,但关闭 StdoutPipe 是一个良好的习惯。
  • c.Wait(): 这是非常重要的一步。它会阻塞当前Go协程,直到子进程退出。如果不在主程序中调用 Wait(),子进程可能会变成僵尸进程。同时,Wait() 也会返回子进程的退出状态,可以用于检查子进程是否正常结束。

完整示例代码

将上述所有片段组合起来,就得到了一个完整的Go程序:

package main

import (
    "bufio"
    "fmt"
    "log"
    "os/exec"
)

func main() {
    // 创建命令对象。-u 标志对于Python是重要的,它强制标准输出和标准错误不进行缓冲。
    c := exec.Command("python", "-u", "add.py")

    // 获取子进程的标准输入管道
    si, err := c.StdinPipe()
    if err != nil {
        log.Fatal("获取StdinPipe失败:", err)
    }

    // 获取子进程的标准输出管道
    so, err := c.StdoutPipe()
    if err != nil {
        log.Fatal("获取StdoutPipe失败:", err)
    }

    // 使用bufio.NewReader包装StdoutPipe,以便按行读取
    reader := bufio.NewReader(so)

    // 启动子进程
    err = c.Start()
    if err != nil {
        log.Fatal("启动子进程失败:", err)
    }

    // 现在进行一些数学运算,与子进程交互10次
    for i := 0; i < 10; i++ {
        // 构造要发送的表达式,并添加换行符
        expr := fmt.Sprintf("2+%d\n", i)

        // 写入数据到子进程的stdin
        _, err = si.Write([]byte(expr))
        if err != nil {
            log.Fatal("写入stdin失败:", err)
        }

        // 从子进程的stdout读取一行响应
        answer, err := reader.ReadString('\n')
        if err != nil {
            log.Fatal("读取stdout失败:", err)
        }

        // 打印结果
        fmt.Printf("对表达式 %q 的答案是 %q\n", expr, answer)
    }

    // 关闭输入管道,通知子进程不再有更多输入
    err = si.Close()
    if err != nil {
        log.Println("关闭StdinPipe失败:", err)
    }

    // 关闭输出管道(可选,但良好实践)
    err = so.Close()
    if err != nil {
        log.Println("关闭StdoutPipe失败:", err)
    }

    // 等待子进程退出并获取其退出状态
    err = c.Wait()
    if err != nil {
        log.Fatal("等待子进程退出失败:", err)
    }

    fmt.Println("子进程已成功退出。")
}

运行效果

将上述Go代码保存为 main.go,Python代码保存为 add.py,并在同一个目录下运行 go run main.go,你将看到如下输出:

对表达式 "2+0\n" 的答案是 "2\n"
对表达式 "2+1\n" 的答案是 "3\n"
对表达式 "2+2\n" 的答案是 "4\n"
对表达式 "2+3\n" 的答案是 "5\n"
对表达式 "2+4\n" 的答案是 "6\n"
对表达式 "2+5\n" 的答案是 "7\n"
对表达式 "2+6\n" 的答案是 "8\n"
对表达式 "2+7\n" 的答案是 "9\n"
对表达式 "2+8\n" 的答案是 "10\n"
对表达式 "2+9\n" 的答案是 "11\n"
子进程已成功退出。

总结

通过 os/exec 包,Go语言提供了强大且灵活的机制来与外部子进程进行交互。实现这种实时双向通信的关键在于:

  1. 正确创建命令:使用 exec.Command 并确保传递必要的参数(如Python的 -u 标志)。
  2. 建立管道:通过 StdinPipe() 和 StdoutPipe() 获取与子进程标准输入输出连接的 io.WriteCloser 和 io.ReadCloser。
  3. 启动进程:调用 c.Start() 启动子进程。
  4. 循环读写:使用 si.Write() 向子进程发送数据,并使用 bufio.NewReader 包装 StdoutPipe 后,通过 reader.ReadString('\n') 等方法读取子进程的响应。确保发送和接收的数据都以换行符结束,以匹配按行处理的逻辑。
  5. 错误处理:在每一步操作后检查 err,确保程序的健壮性。
  6. 资源清理:在交互结束后,务必调用 si.Close() 关闭输入管道,并调用 c.Wait() 等待子进程退出,以避免资源泄露。

掌握这些技术,可以让你在Go程序中无缝集成和控制各种外部工具和脚本,极大地扩展Go应用程序的功能边界。

热门AI工具

更多
VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

354

2023.09.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1818

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2884

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6204

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

6029

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

7634

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

11454

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

5899

2025.07.29

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Uvicorn官方安装与入门手册
Uvicorn官方安装与入门手册

共0课时 | 0人学习

Python uv官方项目管理指南
Python uv官方项目管理指南

共0课时 | 0人学习

Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn