Go 从命名管道连续读取
我想知道为了使用golang连续读取命名管道,我还有哪些其他选项。我当前的代码依赖于在gorutine中运行的无限for循环;但是hat使一个CPU保持100%的使用率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
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的解决方案不是最优的:
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的缓冲区
我的方法
更好的解决方案是等待写入发生,然后立即将命名管道的内容复制到目标文件。在大多数系统上,文件系统事件会有某种通知。该包可用于访问我们感兴趣的事件,因为写入事件在大多数重要操作系统上跨平台工作。另一个我们感兴趣的事件是移除命名管道,因为我们没有