3.1.管道
\(3.1.\)管道
1.系统命令创建
- 创建命令:
1 | cmd0 := exec.Command("echo", "-n", "My first command comes from golang. ") |
- 启动命令:
1 | if err := cmd0.Start(); err != nil { |
2.读取数据
StdoutPipe()
方法返回的对象类型为io.ReadCloser
,这是拓展了io.Reader
接口的接口类型。我们可以调用它的Read
方法获取命令输出:
1 | // 用于存放数据的字节切片 |
如果输出管道没有可以读取的数据,Read
方法返回的err
就会是io.EOF
。
如果切片的长度过小,可以通过for
循环迭代读取:
1 | var outputBuf0 bytes.Buffer |
还可以使用带缓冲的读取器来读取输出管道中的数据,这样更加简便:
1 | outputBuf0 := bufio.NewReader(stdout0) |
由于stdout0
也具有io.Reader
接口类型,它可以作为bufio.NewReader
的参数。
3.创建管道
\(a.\)匿名管道
- 创建获取此命令的管道:
1 | stdout0, err := cmd0.StdoutPipe() |
管道可以把一个命令的输出作为另一个命令的输入,在go中的例子如下:
1 | cmd1 := exec.Command("ps", "aux") |
由于
bytes.Buffer
实现了io.Writer
和io.Reader
接口,因此可以将它赋值给.Stdout
和.Stdin
。
cmd1.wait
会一直阻塞,直到cmd1
完全运行结束。
- 匿名管道会在管道缓冲区被写满之后使写数据的进程阻塞
\(b.\)命名管道
与匿名管道不同,任何进程都可以通过命名管道交换数据。
实际上,命名管道以文件的形式存在于文件系统中,使用它的方法与使用文件很类似。
go中创建这样的管道的API如下:
1 | reader, writer, err := os.Pipe() |
这里返回的reader
、writer
分别为管道输入端、输出端的*os.File
类型值。并发运行下面的代码,就可以让reader
读取writer
写入的数据了:
1 | go func() { |
为什么要并发运行呢?因为管道的两端(读取端和写入端)在某一端未准备就绪时会阻塞:
如果写入端试图写入管道,但管道的缓冲区已满,则写入操作会阻塞,直到读取端读取了足够的数据。
如果读取端试图从管道读取,但管道中没有数据,则读取操作会阻塞,直到写入端写入了数据。
如果顺序执行的话,程序就会一直被阻塞了。
命名管道可以被多路复用,这使得我们需要考虑操作原子性。go的io
包里提供了一个保证原子性的管道:
1 | reader, writer := io.Pipe() |
reader
、writer
分别为*io.PipeReader
和*io.PipeWriter
类型变量。