熟悉 Unix/C 编程的应该对 IPC 也非常的熟悉, 多进程之间的通信主要的手段有管道 / 信号量 / 共享内存 / Socket 等, 而管道作为父子进程间进行少量数据传递的有效手段也得到了广泛的应用, 在这篇文章中我们来看一下 go 语言中如何使用管道进行进程进行通信.
管道的使用
在 Linux 下, 管道被非常广泛地使用, 一般在编程中我们实现了 popen 等的应用即可提供管道功能. 而在命令行中使用地也非常多,| 就是最为典型的管道的应用例子. shell 会为 | 符号两侧的命令各创建一个脚本, 将左侧的输出管道与右侧的输入管道进行连接, 可以进行单向管道通信.
比如我们使用 go env 来确认 go 语言的环境变量, 然后使用 grep 从中确认出 GOROOT 环境变量的值一般会如下这样做
- [root@liumiaocn goprj]# go env |grep GOROOT
- GOROOT="/usr/local/go"
- [root@liumiaocn goprj]#
- 1
- 2
- 3
实现的过程其实 go env 会启动一个进程, 而 grep 命令也会产生一个进程, grep 的进程会在 go env 的标准输出中进行检索 GOROOT 的行的信息然后显示出来, 而负责这两个进程间的通信的正是管道.
在 c 语言中, 我们需要父进程中进行 fork 以及对父进程的基本信息进行处理, 同时初期化连接的管道信息从而实现管道通信. 接下来, 我们来看一下在 go 语言中是如何实现的
调用操作系统命令
为了方便演示, 我们使用标准库 os 中的 API 以调用操作系统的命令并在此基础上建立用于通信的管道
例子代码
- [root@liumiaocn goprj]# cat basic-ipc-pipe.go
- package main
- import "fmt"
- import "os/exec"
- func main() {
- //create cmd
- cmd_go_env := exec.Command("go", "env")
- //cmd_grep:=exec.Command("grep","GOROOT")
- stdout_env, env_error := cmd_go_env.StdoutPipe()
- if env_error != nil {
- fmt.Println("Error happened about standard output pipe", env_error)
- return
- }
- //env_error := cmd_go_env.Start()
- if env_error := cmd_go_env.Start(); env_error != nil {
- fmt.Println("Error happened in execution", env_error)
- return
- }
- a1 := make([]byte, 1024)
- n, err := stdout_env.Read(a1)
- if err != nil {
- fmt.Println("Error happened in reading from stdout", err)
- }
- fmt.Printf("Standard output of go env command: %s", a1[:n])
- }
- [root@liumiaocn goprj]#
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
执行结果
- [root@liumiaocn goprj]# go run basic-ipc-pipe.go
- Standard output of go env command: GOARCH="amd64"
- GOBIN=""GOEXE=""
- GOHOSTARCH="amd64"
- GOHOSTOS="linux"
- GOOS="linux"
- GOPATH=""GORACE=""
- GOROOT="/usr/local/go"
- GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
- CC="gcc"
- GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build142715013=/tmp/go-build -gno-record-gcc-switches"
- CXX="g++"
- CGO_ENABLED="1"
- [root@liumiaocn goprj]#
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
管道连接
通过调用 exec.Start 启动一个进程, 通过 StdoutPipe 将此调用的输出管道也创建了出来, 在这里, 我们读取了此输出的信息, 确实是 go env 命令的标准输出, 接下来要做的事情就是将此输出的管道与 grep 命令的进程进行连接了. 我们将上面的代码进一步充实:
例子代码
- [root@liumiaocn goprj]# cat basic-ipc-pipe.go
- package main
- import "fmt"
- import "os/exec"
- import "bufio"
- import "bytes"
- func main() {
- //create cmd
- cmd_go_env := exec.Command("go", "env")
- cmd_grep := exec.Command("grep", "GOROOT")
- stdout_env, env_error := cmd_go_env.StdoutPipe()
- if env_error != nil {
- fmt.Println("Error happened about standard output pipe", env_error)
- return
- }
- //env_error := cmd_go_env.Start()
- if env_error := cmd_go_env.Start(); env_error != nil {
- fmt.Println("Error happened in execution", env_error)
- return
- }
- /*
- a1 := make([]byte, 1024)
- n, err := stdout_env.Read(a1)
- if err != nil {
- fmt.Println("Error happened in reading from stdout", err)
- return
- }
- fmt.Printf("Standard output of go env command: %s", a1[:n])
- */
- //get the output of go env
- stdout_buf_grep := bufio.NewReader(stdout_env)
- //create input pipe for grep command
- stdin_grep, grep_error := cmd_grep.StdinPipe()
- if grep_error != nil {
- fmt.Println("Error happened about standard input pipe", grep_error)
- return
- }
- //connect the two pipes together
- stdout_buf_grep.WriteTo(stdin_grep)
- //set buffer for reading
- var buf_result bytes.Buffer
- cmd_grep.Stdout = &buf_result
- //grep_error := cmd_grep.Start()
- if grep_error := cmd_grep.Start(); grep_error != nil {
- fmt.Println("Error happened in execution", grep_error)
- return
- }
- err := stdin_grep.Close()
- if err != nil {
- fmt.Println("Error happened in closing pipe", err)
- return
- }
- //make sure all the infor in the buffer could be read
- if err := cmd_grep.Wait(); err != nil {
- fmt.Println("Error happened in Wait process")
- return
- }
- fmt.Println(buf_result.String())
- }
- [root@liumiaocn goprj]#
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
内容非常简单, 无需过多解释, 唯一需要注意的就是上一个例子中为了确认中间信息, 读出管道的信息, 但是此处读完了, 输入管道就读不到了, 所以注释掉了才能正常执行
执行结果
- [root@liumiaocn goprj]# go run basic-ipc-pipe.go
- GOROOT="/usr/local/go"
- [root@liumiaocn goprj]#
- 1
- 2
- 3
- 4
从这里可以看出与 go env |grep GOROOT 是一样的结果.
总结
本文通过模拟非常简单的管道在 go 中实现的例子, 解释了 go 语言中匿名管道的使用方式, 当然和 unix/c 一样, go 也支持命名管道, 通过 os.Pipe() 即可轻松实现, 基本原理均类似, 在此不再赘述.
来源: http://www.bubuko.com/infodetail-2893010.html