英文:
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('\n')
if err != nil {
return err
}
outgoing <- 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 实现支持对 Read 和 Close 进行并发调用,其中 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 <-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
}
}
Calling close(done) from another goroutine will cancel reads on the body.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论