管道(Pipe)

管道(pipe)是一种半双工的(或者说是单向的)通讯方式,它只能被用于父进程和子进程以及同祖先的子进程之间的通讯。

使用管道需注意以下四种情况:

  • 如果所有指向管道写端的文件描述符都关闭了,仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
  • 如果有指向管道写端的文件描述符没关闭,持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
  • 如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
  • 如果有指向管道读端的文件描述符没关闭,持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

匿名管道

原理类似ps aux | grep java

package main

import (
	"bytes"
	"fmt"
	"os/exec"
)

func Ma() {
	cmd1 := exec.Command("ps", "aux")
	cmd2 := exec.Command("grep", "apipe")

	//cmd对象的底层,Stdout与Stdin属性也是通过指向一个字节流实现读写的,这里用新建的字节流代替
	var outputBuf1 bytes.Buffer
	cmd1.Stdout = &outputBuf1
	if err := cmd1.Start(); err != nil {
		fmt.Printf("Error: The first command can not be startup %s\n", err)
		return
	}
	if err := cmd1.Wait(); err != nil { //wait会阻塞cmd直到其运行完毕
		fmt.Printf("Error: Couldn't wait for the first command: %s\n", err)
		return
	}
    
	//cmd1的输出与cmd2的输入指向同一个字节流地址
	cmd2.Stdin = &outputBuf1
	var outputBuf2 bytes.Buffer
	cmd2.Stdout = &outputBuf2
	if err := cmd2.Start(); err != nil {
		fmt.Printf("Error: The second command can not be startup: %s\n", err)
		return
	}
	if err := cmd2.Wait(); err != nil {
		fmt.Printf("Error: Couldn't wait for the second command: %s\n", err)
		return
	}
}

命名管道

原理类似mkfifo -m 777 myfifo cat src.log > myfifo

操作 作用 特性
reader, writer, err := os.Pipe() 创建独立管道 可以被多路复用,不提供原子操作支持
package main

import (
	"fmt"
	"os"
	"time"
)

func Ma() {
	reader, writer, err := os.Pipe()
	if err != nil {
		fmt.Printf("Error: Couldn't create the named pipe: %s\n", err)
	}
	//Read与Write会在另一端还未就绪时对进程进行阻塞,所以二者需要并发运行
	go func() {
		output := make([]byte, 100)
		n, err := reader.Read(output) //会阻塞,等待writer写入
		if err != nil {
			fmt.Printf("Error: Couldn't read data from the named pipe: %s\n", err)
		}
		fmt.Printf("读到了 %d byte(s). [file-based pipe]\n", n)
	}()
	input := make([]byte, 26)
	for i := 65; i <= 90; i++ {
		input[i-65] = byte(i)
	}
	n, err := writer.Write(input) //往管道中写入数据
	if err != nil {
		fmt.Printf("Error: Couldn't write data to the named pipe: %s\n", err)
	}
	fmt.Printf("写入了 %d byte(s). [file-based pipe]\n", n)
	time.Sleep(1 * time.Hour)
}
操作 作用 特性
reader, writer := io.Pipe() 创建内存独立管道 基于内存的提供原子操作保证的管道
package main

import (
	"fmt"
	"io"
	"time"
)

func Ma() {
	reader, writer := io.Pipe()
	go func() {
		output := make([]byte, 100)
		n, err := reader.Read(output)
		if err != nil {
			fmt.Printf("Error: Couldn't read data from the named pipe: %s\n", err)
		}
		fmt.Printf("读到了 %d byte(s). [内存中的管道]\n", n)
	}()
	input := make([]byte, 26)
	for i := 65; i <= 90; i++ {
		input[i-65] = byte(i)
	}
	n, err := writer.Write(input)
	if err != nil {
		fmt.Printf("Error: Couldn't write data to the named pipe: %s\n", err)
	}
	fmt.Printf("写入了 %d byte(s). [内存中的管道]\n", n)
	time.Sleep(200 * time.Millisecond)
}
posted @ 2022-11-15 11:01  厚礼蝎  阅读(255)  评论(0编辑  收藏  举报