context.WithTimeouttime.After
func doBadthing(done chan bool) {
	time.Sleep(time.Second)
	done <- true
}

func timeout(f func(chan bool)) error {
	done := make(chan bool)
	go f(done)
	select {
	case <-done:
		fmt.Println("done")
		return nil
	case <-time.After(time.Millisecond):
		return fmt.Errorf("timeout")
	}
}

// timeout(doBadthing)

对于这段代码,会出现超时,子协程是不能够全部正常退出的。参见如何退出协程 goroutine 超时场景

  • 它的问题是,它使用的是无缓冲的channel,当select执行到超时,doBadthing发送给done的信息没有接收方,因为进到了超时分支,所以,发送方doBadthing会一直阻塞,导致协程不能退出。
    那如果在实际的业务中,我们使用了上述的代码,那越来越多的协程会残留在程序中,最终会导致内存耗尽(每个协程约占 2K 空间),程序崩溃。
  • 如何避免呢?
    1.创建有缓冲区的channel,使得即使没有接收方,发送方也不会发生阻塞。
    2.使用select尝试发送
func doGoodthing(done chan bool) {
	time.Sleep(time.Second)
	select {
	case done <- true:
	default:
		return
	}
}

如果发送失败则,会直接退出。
3.拆分任务,分为多段,配合select使用,判断哪一段超时。

goroutine 被设计为不可以从外部无条件地结束掉,只能通过 channel 来与它通信。也就是说,每一个 goroutine 都需要承担自己退出的责任。(A goroutine cannot be programmatically killed. It can only commit a cooperative suicide.)

参考