似乎不可能通过通道与执行文件操作的 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的引用加锁,但会导致外部代码阻塞,直到挂起的读取完成,并且我特别希望能够中断正在进行的读取。
为了解决这种情况,标准库中的多个 io.ReadCloser 实现支持对 Read 和 Close 的并发调用,其中 Close 会中断活动的 Read。
由 net/http Transport 创建的响应正文读取器就是其中之一。在响应正文上同时调用 Read 和 Close 是安全的。
以下是如何在 body 上使用 close 来实现取消:
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) 将取消对 body 的读取。
对于 HTTP 响应正文的特定情况,您可以使用 cancel context 停止读取响应正文。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
// handle eror
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
// handle error
}
使用
readlines
,如问题所示:
go readLines(resp, outgoing)
通过调用
cancel()
停止读取响应正文。