[golang] io.Pipe()在进程通讯中的使用

定义

func Pipe() (*PipeReader, *PipeWriter)

Pipe creates a synchronous in-memory pipe. It can be used to connect code expecting an io.Reader with code expecting an io.Writer.
Reads and Writes on the pipe are matched one to one except when multiple Reads are needed to consume a single Write. That is, each Write to the PipeWriter blocks until it has satisfied one or more Reads from the PipeReader that fully consume the written data. The data is copied directly from the Write to the corresponding Read (or Reads); there is no internal buffering.
It is safe to call Read and Write in parallel with each other or with Close. Parallel calls to Read and parallel calls to Write are also safe: the individual calls will be gated sequentially.

io.Pipe使用

io.Pipe会返回一个reader和writer,对reader读取(或写入writer)后,进程会被锁住,直到writer有新数据流进入或关闭(或reader把数据读走)。如下面程序会出现死锁

func main() {
  reader, writer := io.Pipe()
  
  // writer写入后会锁住进程
	wrtier.Write([]byte("hello"))
  defer writer.Close()

  // Reader读取后会锁住进程
  buffer := make([]byte, 100)
  reader.Read(buffer)
  println(string(buffer))
}

处理的办法是新建一个goroutine专门写writer

func main() {
  reader, writer := io.Pipe()
  // 创建goroutine给writer
	go func() {
		writer.Write([]byte("hello"))
		defer writer.Close()
	}()
	buffer := make([]byte, 100)
	reader.Read(buffer)
	println(string(buffer))
}

或者

func main() {
	reader, writer := io.Pipe()
	defer writer.Close()
  // 创建goroutine给reader
	go func() {
		buffer := make([]byte, 100)
    reader.Read(buffer)
		println(string(buffer))
	}()
	writer.Write([]byte("hello"))
}

上面这段代码已经可以顺利地从writer中读取中消息了。但是实际运行的过程中我们会发现这段代码在屏幕上没有任何输出。其原因是在启动的子线程中还没执行完毕时,主线程已经结束,并强制结束了其他所有线程,解决的办法是让主线程加上一个锁

func main() {
	reader, writer := io.Pipe()
	defer writer.Close()
	lock := make(chan int)
	// 创建goroutine给reader
	go func() {
		buffer := make([]byte, 100)
		reader.Read(buffer)
		println(string(buffer))
		lock <- 1
	}()
	writer.Write([]byte("hello"))
	<-lock
}

有关信道对goroutine的控制,详见Go语言并发与并行学习笔记(一)。
对于等待子线程结束更优雅的方式,可以使用sync.WaitGroup。详见Go语言实战笔记(十二)| Go goroutine

io.Pipe在进程通讯中的使用

我们以Unix系统中的grep作为例子。命令

$ cat input | grep turtle

将以cat input的输出作为grep turtle的输入,运行grep程序。整个命令的作用就是在问进input中查找带有单词turtle的行。现在我们用go语言实现这个命令的调用

func main() {
	fPtr, _ := os.OpenFile("input", os.O_RDONLY, 0755)
	cmd := exec.Command("grep", "turtle")
	r, w := io.Pipe()
	cmd.Stdin = r
	cmd.Stdout = os.Stdout
	go func() {
		io.Copy(w, fPtr)
		w.Close()
	}()
	cmd.Run()
}

注意到,exec.Command返回的exec.Cmd类型用一个io.Reader接受输入。但在我们的应用中,我们希望命令能得到一个io.Writer,让程序的其他部分往这个Writer上写入数据。而连通io.Reader和io.Writer的桥梁就是我们的os.Pipe()。这里注意一个细节:使用一个新的goroutine写入数据,并在同一个goroutine中关闭Writer

扩展阅读

使用go的io.Pipe优雅的优化中间缓存

你可能感兴趣的:(golang)