Go 从命名管道连续读取

Go 从命名管道连续读取,go,named-pipes,Go,Named Pipes,我想知道为了使用golang连续读取命名管道,我还有哪些其他选项。我当前的代码依赖于在gorutine中运行的无限for循环;但是hat使一个CPU保持100%的使用率 func main() { .... var wg sync.WaitGroup fpipe, _ := os.OpenFile(namedPipe, os.O_RDONLY, 0600) defer fpipe.Close() f, _ := os.Create("dump.txt") defer f.Close() va

我想知道为了使用golang连续读取命名管道,我还有哪些其他选项。我当前的代码依赖于在gorutine中运行的无限for循环;但是hat使一个CPU保持100%的使用率

func main() {
....

var wg sync.WaitGroup
fpipe, _ := os.OpenFile(namedPipe, os.O_RDONLY, 0600)
defer fpipe.Close()

f, _ := os.Create("dump.txt")
defer f.Close()
var buff bytes.Buffer

wg.Add(1)
go func() {
        for {
          io.Copy(&buff, fpipe)
          if buff.Len() > 0 {
              buff.WriteTo(f)
           }
         }
    }()

    wg.Wait()
}

当没有写入程序时,命名管道读取器将接收EOF。此代码之外的解决方案是确保始终有一个writer进程保存文件描述符,尽管它不需要写任何东西

在Go程序中,如果要等待新的写入程序,则必须轮询for循环中的
io.Reader
。您当前的代码是通过一个繁忙的循环来完成的,这将消耗100%的1个cpu核心。添加睡眠和返回其他错误的方法可以解决此问题:

for {
    err := io.Copy(&buff, fpipe)
    if buff.Len() > 0 {
        buff.WriteTo(f)
    }

    if err != nil {
        // something other than EOF happened
        return
    }

    time.Sleep(100 * time.Millisecond)
}

当没有写入程序时,命名管道读取器将接收EOF。此代码之外的解决方案是确保始终有一个writer进程保存文件描述符,尽管它不需要写任何东西

在Go程序中,如果要等待新的写入程序,则必须轮询for循环中的
io.Reader
。您当前的代码是通过一个繁忙的循环来完成的,这将消耗100%的1个cpu核心。添加睡眠和返回其他错误的方法可以解决此问题:

for {
    err := io.Copy(&buff, fpipe)
    if buff.Len() > 0 {
        buff.WriteTo(f)
    }

    if err != nil {
        // something other than EOF happened
        return
    }

    time.Sleep(100 * time.Millisecond)
}
简介 如前所述,如果没有写入程序,命名管道读取器将收到EOF

然而,我发现@JimB的解决方案不是最优的:

  • 命名管道的最大容量(65kB,iirc),很可能在100毫秒的休眠期内被填满。当缓冲区被填满时,所有写入程序都会无缘无故地阻塞
  • 如果重新启动,平均会丢失50毫秒的数据。再一次,没有好的理由
  • 如果您想使用静态缓冲区进行复制,imho将是更好的解决方案。但这甚至不是必需的,因为(或底层实现)实际上分配了32kB的缓冲区
  • 我的方法 更好的解决方案是等待写入发生,然后立即将命名管道的内容复制到目标文件。在大多数系统上,都有关于文件系统事件的某种通知。该包可用于访问我们感兴趣的事件,因为写入事件在大多数重要操作系统上跨平台工作。我们感兴趣的另一个事件是删除命名管道,因为我们没有任何可读取的内容

    因此,我的解决办法是:

    package main
    
    import (
        "flag"
        "io"
        "log"
        "os"
    
        "github.com/rjeczalik/notify"
    )
    
    const (
        MAX_CONCURRENT_WRITERS = 5
    )
    
    var (
        pipePath string
        filePath string
    )
    
    func init() {
        flag.StringVar(&pipePath, "pipe", "", "/path/to/named_pipe to read from")
        flag.StringVar(&filePath, "file", "out.txt", "/path/to/output file")
        log.SetOutput(os.Stderr)
    }
    
    func main() {
        flag.Parse()
    
        var p, f *os.File
        var err error
        var e notify.EventInfo
    
        // The usual stuff: checking wether the named pipe exists etc
        if p, err = os.Open(pipePath); os.IsNotExist(err) {
            log.Fatalf("Named pipe '%s' does not exist", pipePath)
        } else if os.IsPermission(err) {
            log.Fatalf("Insufficient permissions to read named pipe '%s': %s", pipePath, err)
        } else if err != nil {
            log.Fatalf("Error while opening named pipe '%s': %s", pipePath, err)
        }
        // Yep, there and readable. Close the file handle on exit
        defer p.Close()
    
        // Do the same for the output file
        if f, err = os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600); os.IsNotExist(err) {
            log.Fatalf("File '%s' does not exist", filePath)
        } else if os.IsPermission(err) {
            log.Fatalf("Insufficient permissions to open/create file '%s' for appending: %s", filePath, err)
        } else if err != nil {
            log.Fatalf("Error while opening file '%s' for writing: %err", filePath, err)
        }
        // Again, close the filehandle on exit
        defer f.Close()
    
        // Here is where it happens. We create a buffered channel for events which might happen
        // on the file. The reason why we make it buffered to the number of expected concurrent writers
        // is that if all writers would (theoretically) write at once or at least pretty close
        // to each other, it might happen that we loose event. This is due to the underlying implementations
        // not because of go.
        c := make(chan notify.EventInfo, MAX_CONCURRENT_WRITERS)
    
        // Here we tell notify to watch out named pipe for events, Write and Remove events
        // specifically. We watch for remove events, too, as this removes the file handle we
        // read from, making reads impossible
        notify.Watch(pipePath, c, notify.Write|notify.Remove)
    
        // We start an infinite loop...
        for {
            // ...waiting for an event to be passed.
            e = <-c
    
            switch e.Event() {
    
            case notify.Write:
                // If it a a write event, we copy the content of the named pipe to
                // our output file and wait for the next event to happen.
                // Note that this is idempotent: Even if we have huge writes by multiple
                // writers on the named pipe, the first call to Copy will copy the contents.
                // The time to copy that data may well be longer than it takes to generate the events.
                // However, subsequent calls may copy nothing, but that does not do any harm.
                io.Copy(f, p)
    
            case notify.Remove:
                // Some user or process has removed the named pipe,
                // so we have nothing left to read from.
                // We should inform the user and quit.
                log.Fatalf("Named pipe '%s' was removed. Quitting", pipePath)
            }
        }
    }
    
    主程序包
    进口(
    “旗帜”
    “io”
    “日志”
    “操作系统”
    “github.com/rjeczalik/notify”
    )
    常数(
    最大并发写入程序数=5
    )
    变量(
    管道串
    文件路径字符串
    )
    func init(){
    flag.StringVar(&pipePath,“pipe”,“,”/path/to/named_要读取的管道”)
    flag.StringVar(&filePath,“file”,“out.txt”,“/path/to/output file”)
    log.SetOutput(os.Stderr)
    }
    func main(){
    flag.Parse()
    var p,f*os.File
    变量错误
    var e notify.EventInfo
    //通常的工作:检查命名管道是否存在等
    如果p,err=os.Open(管道路径);os.IsNotExist(err){
    log.Fatalf(“命名管道'%s'不存在”,管道路径)
    }如果os.IsPermission(错误)为else{
    log.Fatalf(“权限不足,无法读取命名管道“%s”:%s”,管道路径,错误)
    }否则,如果错误!=零{
    log.Fatalf(“打开命名管道“%s”时出错:%s”,管道路径,错误)
    }
    //是的,在那里并且可读。在退出时关闭文件句柄
    延迟p.关闭()
    //对输出文件执行相同的操作
    如果f,err=os.OpenFile(filePath,os.O_CREATE | os.O_APPEND | os.O_WRONLY,0600);os.IsNotExist(err){
    log.Fatalf(“文件'%s'不存在”,文件路径)
    }如果os.IsPermission(错误)为else{
    log.Fatalf(“权限不足,无法打开/创建文件“%s”以进行附加:%s”,文件路径,错误)
    }否则,如果错误!=零{
    log.Fatalf(“打开文件“%s”进行写入时出错:%err”,文件路径,err)
    }
    //同样,在退出时关闭文件句柄
    延迟f.关闭()
    //我们为可能发生的事件创建一个缓冲通道
    //我们将其缓冲到预期并发写入程序数的原因
    //如果所有的作家(理论上)同时写作,或者至少写得相当接近
    //对于彼此,我们可能会丢失事件。这是由于底层实现
    //不是因为去。
    c:=make(chan notify.EventInfo,最大并发写入数)
    //这里我们告诉notify注意命名管道中的事件,写入和删除事件
    //具体来说,我们也会注意删除事件,因为这会删除我们需要的文件句柄
    //阅读,使阅读变得不可能
    notify.Watch(管道路径,c,notify.Write | notify.Remove)
    //我们开始一个无限循环。。。
    为了{
    //…等待事件通过。
    e=介绍
    如前所述,如果没有写入程序,命名管道读取器将收到EOF

    然而,我发现@JimB的解决方案不是最优的:

  • 命名管道具有最大容量(65kB,iirc),很可能在100毫秒的休眠期内被填满。当缓冲区被填满时,所有写入程序都会无缘无故地阻塞
  • 如果重新启动,您将平均丢失50毫秒的数据。同样,没有充分的理由
  • 如果您想使用静态缓冲区进行复制,imho将是更好的解决方案。但这甚至不是必需的,因为(或底层实现)实际上分配了32kB的缓冲区
  • 我的方法 更好的解决方案是等待写入发生,然后立即将命名管道的内容复制到目标文件。在大多数系统上,文件系统事件会有某种通知。该包可用于访问我们感兴趣的事件,因为写入事件在大多数重要操作系统上跨平台工作。另一个我们感兴趣的事件是移除命名管道,因为我们没有