在Go中,如何关闭长时间运行的读取?

时间:2014-10-18 07:53:58

标签: go goroutine

除非您阻止文件操作上的通道通信,否则通过具有执行文件操作的goroutine的通道进行双向通信似乎不可能。我如何解决这个限制?

另一种表达这个问题的方法......

如果我在goroutine中有一个类似于以下循环的循环,我怎么能告诉它关闭连接并在下一次读取时不停止退出?

func readLines(response *http.Response, outgoing chan string) error {
    defer response.Body.Close()
    reader := bufio.NewReader(response.Body)

    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            return err
        }
        outgoing <- line
    }
}

它不可能从一个告诉它什么时候关闭的频道读取,因为它在网络读取时阻塞(在我的情况下,可能需要几个小时)。

从goroutine外部简单地调用Close()似乎不安全,因为Read / Close方法似乎不是完全线程安全的。

我可以简单地锁定对例程内部/外部使用的response.Body的引用,但是会导致外部代码阻塞,直到挂起的读取完成,我特别希望能够中断正在进行的操作读取。

1 个答案:

答案 0 :(得分:5)

为了解决这种情况,标准库中的几个io.ReadCloser实现支持对ReadClose的并发调用,其中Close会中断活动的Read。

net / http Transport创建的响应正文阅读器就是其中一个实现。同时在响应主体上调用Read和Close是安全的。

您还可以通过调用Transport CancelRequest method来中断响应正文的活动读取。

以下是使用close on body取消工具的方法:

func readLines(response *http.Response, outgoing chan string, done chan struct{}) error {
    cancel := make(chan struct{})
    go func() {
       select {
       case <-done:
          response.Body.Close()
       case <-cancel:
          return
    }()

    defer response.Body.Close()
    defer close(cancel) // ensure that goroutine exits

    reader := bufio.NewReader(response.Body)
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            return err
        }
        outgoing <- line
    }
}

从另一个goroutine调用close(done)将取消对身体的读取。