背景问题:
自Go语言推出以来,提供了time包,其中包含控制时间的主要函数——time.Sleep和time.After。与使用系统睡眠的语言不同,Go通过其原语实现异步计时器,这对于多线程工作非常重要。
问题:
开发者经常错误地使用time.Sleep来实现任务之间的暂停,这在竞争程序中是不合理的。在Go的范式中,更好的做法是通过通道和time.After来管理事件的等待,以便与select/通道集成。
解决方案:
time.Sleep(d)会阻塞当前的goroutine,持续d时间,这是一种直接的“睡眠”。time.After(d)返回一个通道,该通道将在d时间后出现时间事件。后者在select中对通道的使用更加灵活,适合于可中断的等待和超时。
示例代码:
ch := make(chan struct{}) go func() { time.Sleep(2 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("完成") case <-time.After(1 * time.Second): fmt.Println("超时") }
关键特点:
可以使用time.Sleep来阻塞整个程序的执行吗?
不可以,time.Sleep只“睡眠”一条goroutine,其他的继续执行。
如果通道不被读取,time.After会导致内存泄漏吗?
会,计时器会一直挂起,直到值被读取——如果没有读取,垃圾收集器不会删除对象。
示例代码:
func leak() { for { _ = time.After(time.Hour) } }
如何在未收到事件时正确取消对time.After的等待?
最好使用time.Timer并手动停止它,如果需要在到期之前结束等待:
t := time.NewTimer(time.Minute) if done { t.Stop() }
Goroutine等待来自工作的信号,并在超时情况下这样做:
time.Sleep(10*time.Second) doSomething()
优点:
缺点:
代码通过select和time.After构建:
select { case <-workSignal: // 执行 case <-time.After(10 * time.Second): // 超时处理 }
优点:
缺点: