vas*_*man 11 resources go blocking
我试图更多地了解在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:
}
}
Run Code Online (Sandbox Code Playgroud)
从低级别的视图(系统调用,cpu调度)这些等待时间有什么区别?
我的理解是time.Sleep让CPU自由执行其他任务,直到指定的时间结束.阻止股票代码是否<- t.C也这样做?处理器是否轮询通道或是否涉及中断?选择中有多个频道会改变什么吗?
换句话说,假设otherChan从未有任何东西投入其中,这三个选项是否会以相同的方式执行,还是会比其他选项的资源密集程度更低?
mna*_*mna 16
这是一个非常有趣的问题,所以我做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)
Run Code Online (Sandbox Code Playgroud)
没有机构,没有特定于操作系统的定义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) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
回想起来很有意义,因为它必须与goroutine调度程序进行交互.它最终调用goparkunlock,"将goroutine置于等待状态".使用在计时器到期时调用的回调函数time.Sleep创建一个runtime.timer回调函数 - 该回调函数通过调用唤醒goroutine goready.有关详细信息,请参阅下一节runtime.timer.
time.NewTicker创建一个*Ticker(并且time.Tick是一个辅助函数,它做同样的事情,但直接返回*Ticker.C,代码的接收通道,而不是*Ticker,所以你可以用它编写你的代码)有类似的钩子进入运行时:自动收报机是一个结构,持有一个runtimeTimer和一个通道,在其上发出信号.
runtimeTimer在time包中定义,但它必须与timerin 保持同步src/runtime/time.go,所以它实际上是一个runtime.timer.请记住,在time.Sleep计时器中有一个回调函数来唤醒睡眠goroutine?在这种情况下*Ticker,计时器的回调函数在自动收报机的通道上发送当前时间.
然后,真正的等待/调度发生在来自通道的接收上,这基本上与select语句相同,除非otherChan在tick之前发送一些东西,所以让我们看看阻塞接收上发生了什么.
通道src/runtime/chan.go由hchanstruct 实现(现在在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) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
这部分有很多不同的情况,但在你的例子中,它是从异步通道的阻塞接收(time.NewTicker创建一个缓冲区为1的通道),但无论如何它最终调用... goparkunlock,再次允许其他goroutines在这个等待的时候继续进行.
在所有情况下,goroutine最终被停放(这不是真的令人震惊 - 它无法取得进展,因此如果有任何可用的话,它必须让其线程可用于不同的goroutine).对代码的一瞥似乎表明该通道比直接通道有更多的开销time.Sleep.但是,它允许更强大的模式,例如示例中的最后一个模式:goroutine可以被另一个通道唤醒,以先到者为准.
为了回答你的其他问题,关于轮询,定时器由一个goroutine管理,它会一直睡到它的队列中的下一个定时器,所以它只有在知道必须触发定时器时才能工作.当下一个计时器到期时,它会唤醒调用的goroutine time.Sleep(或者在自动收报机的通道上发送值,它会执行回调函数所做的任何操作).
通道中没有轮询,当chansend在chan.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)
}
Run Code Online (Sandbox Code Playgroud)
这是一个有趣的潜入Go的源代码,非常有趣的问题!希望我至少回答了部分内容!