In Go, how do I close a long running read?

huangapple go评论83阅读模式
英文:

In Go, how do I close a long running read?

问题

似乎不可能通过通道与执行文件操作的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的引用内外部放置一个锁,但这会导致外部代码阻塞,直到挂起的读取完成,而我特别希望能够中断正在进行的读取操作。

英文:

It doesn't seem possible to have two way communication via channels with a goroutine which is performing file operations, unless you block the channel communication on the file operations. How can I work around the limits this imposes?

Another way to phrase this question...

If I have a loop similar to the following running in a goroutine, how can I tell it to close the connection and exit without blocking on the next Read?

func readLines(response *http.Response, outgoing chan string) error {
   	defer response.Body.Close()
   	reader := bufio.NewReader(response.Body)
    
    for {
        line, err := reader.ReadString(&#39;\n&#39;)
    	if err != nil {
    	    return err
    	}
        outgoing &lt;- line
    }
}

It's not possible for it to read from a channel that tells it when to close down because it's blocking on the network reads (in my case, that can take hours).

It doesn't appear to be safe to simply call Close() from outside the goroutine, since the Read/Close methods don't appear to be fully thread safe.

I could simply put a lock around references to response.Body that used inside/outside the routine, but would cause the external code to block until a pending read completes, and I specifically want to be able to interrupt an in-progress read.

答案1

得分: 7

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

net/http 的 Transport 创建的响应体读取器就是其中之一。可以安全地同时调用响应体的 Read 和 Close。

还可以通过调用 Transport CancelRequest 方法 来中断响应体上的活动 Read。

以下是使用关闭来实现取消的示例代码:

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) // 确保 goroutine 退出

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

从另一个 goroutine 中调用 close(done) 将取消对响应体的读取操作。

英文:

To address this scenario, several io.ReadCloser implementations in the standard library support concurrent calls to Read and Close where Close interrupts an active Read.

The response body reader created by net/http Transport is one of those implementations. It is safe to concurrently call Read and Close on the response body.

You can also interrupt an active Read on the response body by calling the Transport CancelRequest method.

Here's how implement cancel using close on the body:

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

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

   	reader := bufio.NewReader(response.Body)
    for {
        line, err := reader.ReadString(&#39;\n&#39;)
    	if err != nil {
    	    return err
    	}
        outgoing &lt;- line
    }
}

Calling close(done) from another goroutine will cancel reads on the body.

huangapple
  • 本文由 发表于 2014年10月18日 15:53:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/26437536.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定