1 回答
TA贡献1871条经验 获得超13个赞
这是一个非常有趣的问题,所以我cd进入了我的 Go 源代码开始寻找。
时间.睡眠
time.Sleep 定义如下:
// src/time/sleep.go
// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)
没有正文,没有特定于操作系统的定义time_unix.go!?!稍微搜索一下,答案是因为time.Sleep实际上是在运行时中定义的:
// src/runtime/time.go
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
// ...
}
回想起来,这很有意义,因为它必须与 goroutine 调度程序进行交互。它最终调用goparkunlock,它“将 goroutine 置于等待状态”。time.Sleep创建一个runtime.timer带有回调函数的回调函数,该函数在计时器到期时调用 - 该回调函数通过调用goready. 有关runtime.timer.
时间.NewTicker
time.NewTicker创建一个*Ticker(并且time.Tick是一个辅助函数,它做同样的事情但直接返回*Ticker.C,股票代码的接收通道,而不是*Ticker,所以你可以用它来编写你的代码)在运行时有类似的钩子:股票代码是一个结构持有一个runtimeTimer和一个通道,在其上发出信号。
runtimeTimer在time包中定义,但它必须与timerin保持同步src/runtime/time.go,因此它实际上是一个runtime.timer. 还记得在time.Sleep,定时器有一个回调函数来唤醒休眠的 goroutine 吗?在 的情况下*Ticker,定时器的回调函数在股票代码的通道上发送当前时间。
然后,真正的等待/调度发生在从通道接收时,这与select语句基本相同,除非otherChan在滴答之前发送一些东西,所以让我们看看阻塞接收时会发生什么。
<- 陈
通道src/runtime/chan.go由hchan结构体在,中实现(现在在 Go 中!)。通道操作具有匹配功能,接收是通过chanrecv以下方式实现的:
// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
func chanrecv(t *chantype, c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// ...
}
这部分有很多不同的情况,但在您的示例中,它是来自异步通道的阻塞接收(time.NewTicker创建一个缓冲区为 1 的通道),但无论如何它最终会调用... goparkunlock,再次允许其他 goroutines在这个被卡住等待时继续。
所以...
在所有情况下,goroutine 最终都会被停住(这并不令人震惊 - 它无法取得进展,因此如果有任何可用的 goroutine,它必须保留其线程可用于不同的 goroutine)。看一眼代码似乎表明该通道的开销比直接time.Sleep. 但是,它允许更强大的模式,例如您示例中的最后一个:goroutine 可以被另一个通道唤醒,以先到者为准。
要回答关于轮询的其他问题,定时器由一个 goroutine 管理,该协程在队列中的下一个定时器之前一直处于休眠状态,因此它仅在知道必须触发定时器时才工作。当下一个计时器到期时,它会唤醒调用的 goroutine time.Sleep(或在股票代码的通道上发送值,它会执行回调函数所做的任何事情)。
频道中没有轮询,当在频道上进行发送时,接收被解锁,在chansendchan.go 文件中:
// wake up a waiting receiver
sg := c.recvq.dequeue()
if sg != nil {
recvg := sg.g
unlock(&c.lock)
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
goready(recvg, 3)
} else {
unlock(&c.lock)
}
这是对 Go 源代码的一个有趣的探索,非常有趣的问题!希望我至少回答了一部分!
- 1 回答
- 0 关注
- 166 浏览
添加回答
举报