如何为循环扫描的 bufio 扫描仪设置超时?

How to set a timeout for a bufio scanner that scans in a loop?

提问人:leetom 提问时间:8/15/2023 更新时间:8/15/2023 访问量:122

问:

我正在使用 golang 启动一个进程并监控输出。该过程将运行很长时间,我需要能够发送信号来结束它。

我有以下代码,在大多数情况下都能很好地工作。但是,由于某种原因,该进程可能没有输出,并且 for 循环将被方法阻塞并且无法从 接收。Scan()processFinishChan

有没有一种简单的方法可以为方法设置超时?我尝试了一个在另一个 goroutine 中运行的解决方案,并使用另一个选择从新的 goroutine 和超时通道接收,但考虑到外部循环,会不会有越来越多的 goroutine 被阻止?Scan()Scan()forScan

// code that start the process...
scanner := bufio.NewScanner(stdout)

for {
    select {
    case <-processFinishChan: // send to this channel to terminate the process
        log.Println("Killing Process")
        err := cmdObject.Process.Kill()
        if err != nil {
            log.Printf("Error Killing: %v", err)
        } else {
            return
        }
    default:
        // default case, read the output of process and send to user.
        if !scanner.Scan() && scanner.Err() == nil {
            // reach EOF
            return
        }
        m := scanner.Bytes()

        WSOutChanHolder.mu.Lock()
        for _, ch := range WSOutChanHolder.data {
            ch <- m
        }
        WSOutChanHolder.mu.Unlock()
    }
}
IO Go例程

评论

0赞 mooga 8/15/2023
您可以使用内部选择,并进行例程扫描time.After()
0赞 leetom 8/15/2023
@mooga我试过了,但方法仍然存在,goroutine 不会结束,会有更多死的 goroutines。Scan

答:

1赞 Dylan Reimerink 8/15/2023 #1

假设是 的结果,读取器应关闭读取器,并在进程退出后等待时中断任何正在进行的读取。stdoutcmdObject.StdoutPipe()

Wait 将在看到命令退出后关闭管道,因此大多数调用者不需要自己关闭管道。

因此,我们需要在单独的 goroutine 中杀死进程,然后在杀死进程后观察它发生并关闭阅读器。Wait

// code that start the process...
scanner := bufio.NewScanner(stdout)

go func() {
    <-processFinishChan: // send to this channel to terminate the process
    log.Println("Killing Process")
    err := cmdObject.Process.Kill()
    if err != nil {
        log.Printf("Error Killing: %v", err)
    } 

    cmdObject.Wait()
} ()

for {
    // default case, read the output of process and send to user.
    if !scanner.Scan() && scanner.Err() == nil {
        // reach EOF
        return
    }
    m := scanner.Bytes()

    WSOutChanHolder.mu.Lock()
    for _, ch := range WSOutChanHolder.data {
        ch <- m
    }
    WSOutChanHolder.mu.Unlock()
}

评论

0赞 leetom 8/15/2023
非常感谢,它有效。顺便说一句,我看到了你的答案,但我没有找到 or 的等价物,那么有没有办法实现“阅读 3 秒,如果什么都不读,继续下一行”之类的东西?SetReadDeadlineio.ReadCloserio.Reader
0赞 Dylan Reimerink 8/15/2023
起初我以为 var 来自 ,但后来意识到它来自一个子进程。返回的是没有此类设施的读取端。管道的写入端安装在 pkg.go.dev/os/exec#Cmd 中。你总是可以实现一个自定义的编写器/管道,这将允许你设置一个上下文,但这需要稍微多一些的工作。stdoutos.StdoutReadCloserio.Pipecmd.Stdout