睡眠和选择的行为

我试图更多地了解在 Go 中各种阻塞/等待类型的操作期间表面下发生的事情。以下面的例子为例:


otherChan = make(chan int)

t = time.NewTicker(time.Second)

for {

    doThings()


    // OPTION A: Sleep

    time.Sleep(time.Second)


    // OPTION B: Blocking ticker

    <- t.C


    // OPTION C: Select multiple

    select {

        case <- otherChan:

        case <- t.C:

    }

}

从底层来看(系统调用、cpu 调度),这些等待时的区别是什么?


我的理解是time.Sleep让 CPU 可以自由执行其他任务,直到指定的时间过去。阻塞自动收报机<- t.C也做同样的事情吗?处理器是否在轮询通道或是否涉及中断?选择中有多个频道会改变什么吗?


换句话说,假设otherChan从来没有放入任何东西,这三个选项是否会以相同的方式执行,或者一个比其他的资源密集度更低?


慕丝7291255
浏览 167回答 1
1回答

慕桂英4014372

这是一个非常有趣的问题,所以我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.Sleepfunc timeSleep(ns int64) {&nbsp; // ...}回想起来,这很有意义,因为它必须与 goroutine 调度程序进行交互。它最终调用goparkunlock,它“将 goroutine 置于等待状态”。time.Sleep创建一个runtime.timer带有回调函数的回调函数,该函数在计时器到期时调用 - 该回调函数通过调用goready. 有关runtime.timer.时间.NewTickertime.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) {&nbsp; // ...}这部分有很多不同的情况,但在您的示例中,它是来自异步通道的阻塞接收(time.NewTicker创建一个缓冲区为 1 的通道),但无论如何它最终会调用... goparkunlock,再次允许其他 goroutines在这个被卡住等待时继续。所以...在所有情况下,goroutine 最终都会被停住(这并不令人震惊 - 它无法取得进展,因此如果有任何可用的 goroutine,它必须保留其线程可用于不同的 goroutine)。看一眼代码似乎表明该通道的开销比直接time.Sleep. 但是,它允许更强大的模式,例如您示例中的最后一个:goroutine 可以被另一个通道唤醒,以先到者为准。要回答关于轮询的其他问题,定时器由一个 goroutine 管理,该协程在队列中的下一个定时器之前一直处于休眠状态,因此它仅在知道必须触发定时器时才工作。当下一个计时器到期时,它会唤醒调用的 goroutine time.Sleep(或在股票代码的通道上发送值,它会执行回调函数所做的任何事情)。频道中没有轮询,当在频道上进行发送时,接收被解锁,在chansendchan.go 文件中:// wake up a waiting receiversg := c.recvq.dequeue()if sg != nil {&nbsp; &nbsp; recvg := sg.g&nbsp; &nbsp; unlock(&c.lock)&nbsp; &nbsp; if sg.releasetime != 0 {&nbsp; &nbsp; &nbsp; &nbsp; sg.releasetime = cputicks()&nbsp; &nbsp; }&nbsp; &nbsp; goready(recvg, 3)} else {&nbsp; &nbsp; unlock(&c.lock)}这是对 Go 源代码的一个有趣的探索,非常有趣的问题!希望我至少回答了一部分!
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go