golang NewTimer/NewTicker 源码阅读

zcxzcxczcx · · 1198 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.引出 src/runtime/time.go 中的 startTimer

 NewTimer 和  NewTicker 分别在 src/time/sleep.go、src/time/tick.go 这两个文件下。这两个函数最主要的区别是NewTimer在初始化runtimeTimer的时候没有初始化period属性。不管是 NewTimer 还是 NewTicker 最终的实现都是调用 startTimer,startTimer 在 src/time/sleep.go 下没有函数体的,它的实现是在 src/runtime/time.go 中的 startTimer,这两者是通过 go:linkname 指令关联的。

2.阅读 src/runtime/time.go 中的 startTimer 

看源码可知 startTimer 调用了addtimer,而 addtimer 调用了 assignBucket 和addtimerLocked。

我们可以先看 assignBucket。从 assignBucket 的源码可知所有的 timer 对象都在一个固定大小为64的桶数组里面,每一个数组元素里面有一个桶对象 timersBucket,从 timersBucket 这个数据结构可以看出这个桶对象包含一个最小堆和一个 loop 循环协程,而 timer 对象归哪个桶管理取决于申请该 timer 对象时 G 所在的 P(通过P的id取余64作为桶数组下标)。

我们再来看 addtimerLocked,addtimerLocked 主要调用了addtimerLocked,所以我们主要看 addtimerLocked。看 addtimerLocked 这个函数的源码可知其实就是把 timer 对象加到这个timer 所属桶的 timer 对象堆里,然后最小堆根据桶内所有 timer 的超时触发绝对时间点做调整。另外,和桶一对一关联的桶协程是懒开启的,只在桶被初次使用时(即有 timer 对象 hash 到了这个桶)才开启,开启后桶协程内部的循环永远不会退出。


最后我们来看这个桶协程实现 timerproc。这里有两层循环,先来看第二层循环:不断的取最小堆里面的第一个 timer 最小对象,如果没有 timer 最小对象没有超时,则 break,退出第二层循环,进入第一层循环。如果超时了,period 大于0则根据 period 延长 when ,period 小于等于0则从这个桶的最小堆里把这个 timer 对象删除。之后然后 siftdownTimer 然后再往通道发送信息 f(arg, seq)。当桶内没 timer 时,桶协程被挂起,即 rescheduling 状态。当桶内还有 timer 时,桶内协程睡眠直到最小超时触发时间点后再唤醒,即 sleeping 状态。当往桶内加入新 timer 而该 timer 的超时触发时间点正好是当前桶内最小的,则唤醒桶协程,让桶协程重新判断,设置新的最小超时触发时间点后进入 sleeping 状态。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:zcxzcxczcx

查看原文:golang NewTimer/NewTicker 源码阅读

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1198 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传