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

问题描述 投票:0回答:1

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

go goroutine
1个回答
7
投票

为了解决这种情况,标准库中的多个 io.ReadCloser 实现支持对 ReadClose 的并发调用,其中 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()
停止读取响应正文。

© www.soinside.com 2019 - 2024. All rights reserved.