超时后停止 goroutine 执行

15

我希望在超时时停止goroutine执行。但似乎对我不起作用。我正在使用iris框架。

  type Response struct {
    data   interface{}
    status bool
  }

  func (s *CicService) Find() (interface{}, bool) {

    ch := make(chan Response, 1)

    go func() {
      time.Sleep(10 * time.Second)

      fmt.Println("test")
      fmt.Println("test1")

      ch <- Response{data: "data", status: true}
    }()

    select {
    case <-ch:
      fmt.Println("Read from ch")
      res := <-ch
      return res.data, res.status
    case <-time.After(50 * time.Millisecond):
      return "Timed out", false
    }

  }

输出:

 Timed out
 test
 test1

期望输出:

 Timed out

有人能指出这里缺少了什么吗?它确实超时了,但仍然运行goroutine以打印 test test1 。我只想在超时时尽快停止goroutine的执行。

3个回答

14

在 goroutine 执行期间没有很好的方法可以“中断”它。

Go 使用 fork-join 并发模型,这意味着您需要“fork”(创建)一个新的 goroutine,然后在达到“join point”之前无法控制该 goroutine 的调度。join point 是多个 goroutine 之间的某种同步点,例如在通道上发送一个值。

就您具体的示例而言,这一行:

ch <- Response{data: "data", status: true}

因为这是一个带缓冲的通道,所以无论如何都能发送值。但你创建的超时:

case <-time.After(50 * time.Millisecond):
  return "Timed out", false

这些超时是在通道的“接收者”或“读取器”上进行的,而不是在“发送者”上进行的。正如在本答案顶部提到的那样,如果没有使用一些同步技术,就没有办法中断goroutine的执行。

由于超时是在从通道“读取”的goroutine上设置的,因此没有任何东西可以阻止发送通道的goroutine的执行。


12

控制 goroutine 处理 的最佳方式是使用 context (标准 Go 库)。

您可以在goroutine内部取消某些操作并停止执行,而无需担心goroutine泄漏的情况

这里有一个简单的示例,可用于按超时取消操作。

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

ch := make(chan Response, 1)

go func() {
    time.Sleep(1 * time.Second)

    select {
    default:
        ch <- Response{data: "data", status: true}
    case <-ctx.Done():
        fmt.Println("Canceled by timeout")
        return
    }
}()

select {
case <-ch:
    fmt.Println("Read from ch")
case <-time.After(500 * time.Millisecond):
    fmt.Println("Timed out")
}

1
这个例子是行不通的。即使你将超时设置为 case <-time.After(50 * time.Second),你也永远无法从 ch 中读取数据。 - MikeKlemin
1
谢谢@MikeKlemin,我已经更新了我的答案以修正行为。 - Oleksandr Mosur
另外,虽然睡眠只是“工作在这里”的示例,但如果一个人真的需要睡眠(例如在轮询周期之间等待),最好创建一个时间通道,例如 <-time.After(10*time.Second) 并将其添加到 select 语句中。这样甚至可以中断睡眠。 - colm.anseo
1
这个例子只对 time.Sleep 有用。如果有一个长时间运行的函数,它无法中断它(没有提前终止)。示例。这个例子中没有打印输出。请使用得票最高的答案。 - Logan W
警告:这不会停止go例程,直到它到达检查ctx.Done()的点。 - user2693017
如果 goroutine 中的默认情况永远阻塞会怎么样? - SolskGaer

0

你有一个gouroutine泄漏,你必须处理一些完成的操作,在超时之前返回goroutine,像这样:

func (s *CicService) Find() (interface{}, bool) {

    ch := make(chan Response, 1)
    done := make(chan struct{})
    go func() {
        select {
        case <-time.After(10 * time.Second):
        case <-done:
            return
        }
        fmt.Println("test")
        fmt.Println("test1")
        ch <- Response{data: "data", status: true}
    }()
    select {
    case res := <-ch:
        return res.data, res.status
    case <-time.After(50 * time.Millisecond):
        done <- struct{}{}
        return "Timed out", false
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接