Go 1.14为了优化计时器的性能,将计时器和epoll耦合在一起了~
创建一个timer时,也会进到epoll的初始化(新timer的实现依赖了epoll的实现)
现在的timer实现有点复杂了~
一次性定时器和周期性定时器
n个定时器需要n个协程吗?
即是否每个定时器,都需要后台有一个协程来专门维护?? 如不如此,是如何做到的?
参考 Go语言中的定时器–简书,验证如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package main
import ( "fmt" "os" "runtime/debug" "time" )
func main() { debug.SetTraceback("system")
fmt.Println("os.Args:", os.Args) fmt.Println("len(os.Args):", len(os.Args))
if len(os.Args) == 1 { panic("before timers") }
for i := 0; i < 10000; i++ { time.AfterFunc(5*time.Second, func() { fmt.Println("Hello!", i) }) } panic("after timers") }
|
如果不带参数执行,即go run xxx.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| os.Args: [/var/folders/9t/839s3jmj73bcgyp5x_xh3gw00000gn/T/go-build3283038849/b001/exe/panic] len(os.Args): 1 panic: before timers
goroutine 1 [running]: panic(0x1007730c0, 0x10078ce68) /usr/local/go/src/runtime/panic.go:1065 +0x4d8 fp=0x1400011fed0 sp=0x1400011fe00 pc=0x1006de848 main.main() /Users/fliter/go/src/shuang/ticker/panic.go:19 +0x22c fp=0x1400011ff70 sp=0x1400011fed0 pc=0x1007466fc runtime.main() /usr/local/go/src/runtime/proc.go:225 +0x26c fp=0x1400011ffd0 sp=0x1400011ff70 pc=0x1006e104c runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x1400011ffd0 sp=0x1400011ffd0 pc=0x10070e5e4
goroutine 2 [force gc (idle)]: runtime.gopark(0x10078cb20, 0x100805f00, 0x1411, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x14000042fa0 sp=0x14000042f80 pc=0x1006e1450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.forcegchelper() /usr/local/go/src/runtime/proc.go:276 +0xbc fp=0x14000042fd0 sp=0x14000042fa0 pc=0x1006e12dc runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x14000042fd0 sp=0x14000042fd0 pc=0x10070e5e4 created by runtime.init.6 /usr/local/go/src/runtime/proc.go:264 +0x30
goroutine 3 [GC sweep wait]: runtime.gopark(0x10078cb20, 0x100806020, 0x140c, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x140000437a0 sp=0x14000043780 pc=0x1006e1450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.bgsweep(0x14000026070) /usr/local/go/src/runtime/mgcsweep.go:163 +0xb0 fp=0x140000437d0 sp=0x140000437a0 pc=0x1006cee30 runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x140000437d0 sp=0x140000437d0 pc=0x10070e5e4 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:217 +0x54
goroutine 4 [GC scavenge wait]: runtime.gopark(0x10078cb20, 0x1008060c0, 0x140d, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x14000043f70 sp=0x14000043f50 pc=0x1006e1450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.bgscavenge(0x14000026070) /usr/local/go/src/runtime/mgcscavenge.go:265 +0xec fp=0x14000043fd0 sp=0x14000043f70 pc=0x1006cd24c runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x14000043fd0 sp=0x14000043fd0 pc=0x10070e5e4 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:218 +0x74
goroutine 5 [finalizer wait]: runtime.gopark(0x10078cb20, 0x100833310, 0x14000021410, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x14000042730 sp=0x14000042710 pc=0x1006e1450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.runfinq() /usr/local/go/src/runtime/mfinal.go:175 +0xb8 fp=0x140000427d0 sp=0x14000042730 pc=0x1006c4a48 runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x140000427d0 sp=0x140000427d0 pc=0x10070e5e4 created by runtime.createfing /usr/local/go/src/runtime/mfinal.go:156 +0x78 exit status 2
|
如果带参数执行,即go run xxx.go -666:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| os.Args: [/var/folders/9t/839s3jmj73bcgyp5x_xh3gw00000gn/T/go-build466442962/b001/exe/panic -123] len(os.Args): 2 panic: after timers
goroutine 1 [running]: panic(0x104e6f0c0, 0x104e88e78) /usr/local/go/src/runtime/panic.go:1065 +0x4d8 fp=0x14000096ed0 sp=0x14000096e00 pc=0x104dda848 main.main() /Users/fliter/go/src/shuang/ticker/panic.go:28 +0x210 fp=0x14000096f70 sp=0x14000096ed0 pc=0x104e426e0 runtime.main() /usr/local/go/src/runtime/proc.go:225 +0x26c fp=0x14000096fd0 sp=0x14000096f70 pc=0x104ddd04c runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x14000096fd0 sp=0x14000096fd0 pc=0x104e0a5e4
goroutine 2 [force gc (idle)]: runtime.gopark(0x104e88b20, 0x104f01f00, 0x1411, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x14000042fa0 sp=0x14000042f80 pc=0x104ddd450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.forcegchelper() /usr/local/go/src/runtime/proc.go:276 +0xbc fp=0x14000042fd0 sp=0x14000042fa0 pc=0x104ddd2dc runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x14000042fd0 sp=0x14000042fd0 pc=0x104e0a5e4 created by runtime.init.6 /usr/local/go/src/runtime/proc.go:264 +0x30
goroutine 17 [GC sweep wait]: runtime.gopark(0x104e88b20, 0x104f02020, 0x140c, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x1400003e7a0 sp=0x1400003e780 pc=0x104ddd450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.bgsweep(0x1400008c000) /usr/local/go/src/runtime/mgcsweep.go:163 +0xb0 fp=0x1400003e7d0 sp=0x1400003e7a0 pc=0x104dcae30 runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x1400003e7d0 sp=0x1400003e7d0 pc=0x104e0a5e4 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:217 +0x54
goroutine 18 [GC scavenge wait]: runtime.gopark(0x104e88b20, 0x104f020c0, 0x140d, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x1400003ef70 sp=0x1400003ef50 pc=0x104ddd450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.bgscavenge(0x1400008c000) /usr/local/go/src/runtime/mgcscavenge.go:265 +0xec fp=0x1400003efd0 sp=0x1400003ef70 pc=0x104dc924c runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x1400003efd0 sp=0x1400003efd0 pc=0x104e0a5e4 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:218 +0x74
goroutine 3 [finalizer wait]: runtime.gopark(0x104e88b20, 0x104f2f310, 0x1410, 0x1) /usr/local/go/src/runtime/proc.go:336 +0xd0 fp=0x14000043730 sp=0x14000043710 pc=0x104ddd450 runtime.goparkunlock(...) /usr/local/go/src/runtime/proc.go:342 runtime.runfinq() /usr/local/go/src/runtime/mfinal.go:175 +0xb8 fp=0x140000437d0 sp=0x14000043730 pc=0x104dc0a48 runtime.goexit() /usr/local/go/src/runtime/asm_arm64.s:1130 +0x4 fp=0x140000437d0 sp=0x140000437d0 pc=0x104e0a5e4 created by runtime.createfing /usr/local/go/src/runtime/mfinal.go:156 +0x78 exit status 2
|
两次都是5个goroutine
实际上,亲测出的结果,即便新增了1万个定时器,也没有新增一个goroutine
以上代码在基于amd64的Mac M1上执行,go版本为1.16
使用amd64的机器,go 1.11上运行,则符合原文预期,即不起定时器前4个goroutine,之后5个goroutine:
即
1 2 3
| go run xxxx.go 2>&1 | grep "^goroutine" | wc -l
4
|
1 2 3
| go run xxxx.go -123 2>&1 | grep "^goroutine" | wc -l
5
|
直接run,具体输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| os.Args: [/tmp/go-build548947945/b001/exe/ticker] len(os.Args): 1 panic: before timers
goroutine 1 [running]: panic(0x49f060, 0x4d3ee0) /usr/local/go/src/runtime/panic.go:556 +0x2cb fp=0xc00006af10 sp=0xc00006ae80 pc=0x4270db main.main() /home/ubuntu/go_lab/ticker.go:19 +0x1fd fp=0xc00006af98 sp=0xc00006af10 pc=0x48ea5d runtime.main() /usr/local/go/src/runtime/proc.go:201 +0x207 fp=0xc00006afe0 sp=0xc00006af98 pc=0x428e07 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc00006afe8 sp=0xc00006afe0 pc=0x4515c1
goroutine 2 [runnable]: runtime.forcegchelper() /usr/local/go/src/runtime/proc.go:243 fp=0xc000032fe0 sp=0xc000032fd8 pc=0x428fb0 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc000032fe8 sp=0xc000032fe0 pc=0x4515c1 created by runtime.init.4 /usr/local/go/src/runtime/proc.go:240 +0x35
goroutine 3 [GC sweep wait]: runtime.gopark(0x4c7058, 0x55cfa0, 0x41140c, 0x1) /usr/local/go/src/runtime/proc.go:302 +0xeb fp=0xc000033780 sp=0xc000033760 pc=0x4291eb runtime.goparkunlock(0x55cfa0, 0x4d140c, 0x1) /usr/local/go/src/runtime/proc.go:308 +0x53 fp=0xc0000337b0 sp=0xc000033780 pc=0x429293 runtime.bgsweep(0xc00004a000) /usr/local/go/src/runtime/mgcsweep.go:52 +0x8f fp=0xc0000337d8 sp=0xc0000337b0 pc=0x41cc8f runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc0000337e0 sp=0xc0000337d8 pc=0x4515c1 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:216 +0x58
goroutine 4 [runnable]: runtime.runfinq() /usr/local/go/src/runtime/mfinal.go:161 fp=0xc000033fe0 sp=0xc000033fd8 pc=0x414700 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc000033fe8 sp=0xc000033fe0 pc=0x4515c1 created by runtime.createfing /usr/local/go/src/runtime/mfinal.go:156 +0x61 exit status 2
|
带参数之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| os.Args: [/tmp/go-build829376033/b001/exe/ticker -111] len(os.Args): 2 panic: after timers
goroutine 1 [running]: panic(0x49f060, 0x4d3ef0) /usr/local/go/src/runtime/panic.go:556 +0x2cb fp=0xc00006af10 sp=0xc00006ae80 pc=0x4270db main.main() /home/ubuntu/go_lab/ticker.go:28 +0x1df fp=0xc00006af98 sp=0xc00006af10 pc=0x48ea3f runtime.main() /usr/local/go/src/runtime/proc.go:201 +0x207 fp=0xc00006afe0 sp=0xc00006af98 pc=0x428e07 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc00006afe8 sp=0xc00006afe0 pc=0x4515c1
goroutine 2 [runnable]: runtime.forcegchelper() /usr/local/go/src/runtime/proc.go:243 fp=0xc000030fe0 sp=0xc000030fd8 pc=0x428fb0 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc000030fe8 sp=0xc000030fe0 pc=0x4515c1 created by runtime.init.4 /usr/local/go/src/runtime/proc.go:240 +0x35
goroutine 3 [GC sweep wait]: runtime.gopark(0x4c7058, 0x55cfa0, 0x41140c, 0x1) /usr/local/go/src/runtime/proc.go:302 +0xeb fp=0xc000031780 sp=0xc000031760 pc=0x4291eb runtime.goparkunlock(0x55cfa0, 0x4d140c, 0x1) /usr/local/go/src/runtime/proc.go:308 +0x53 fp=0xc0000317b0 sp=0xc000031780 pc=0x429293 runtime.bgsweep(0xc00004a000) /usr/local/go/src/runtime/mgcsweep.go:52 +0x8f fp=0xc0000317d8 sp=0xc0000317b0 pc=0x41cc8f runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc0000317e0 sp=0xc0000317d8 pc=0x4515c1 created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:216 +0x58
goroutine 4 [runnable]: runtime.runfinq() /usr/local/go/src/runtime/mfinal.go:161 fp=0xc000031fe0 sp=0xc000031fd8 pc=0x414700 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc000031fe8 sp=0xc000031fe0 pc=0x4515c1 created by runtime.createfing /usr/local/go/src/runtime/mfinal.go:156 +0x61
goroutine 5 [runnable]: runtime.timerproc(0x55fb80) /usr/local/go/src/runtime/time.go:224 fp=0xc0000307d8 sp=0xc0000307d0 pc=0x442d00 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1333 +0x1 fp=0xc0000307e0 sp=0xc0000307d8 pc=0x4515c1 created by runtime.(*timersBucket).addtimerLocked /usr/local/go/src/runtime/time.go:170 +0x114 exit status 2
|
原文中使用的命令如下:
go run xxx.go 2>&1 | grep “^goroutine” | wc -l
https://studygolang.com/articles/21597
https://www.cnblogs.com/qggg/p/8571808.html
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-timer/
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-netpoller/
http://xiaorui.cc/archives/6483
https://www.google.com.hk/search?q=site:http://xiaorui.cc/+%E5%AE%9A%E6%97%B6%E5%99%A8&newwindow=1&sxsrf=AOaemvIfh6H5ii2THKYpt0lZfLNlN_ULIg:1635769019251&ei=u9p_YezPDtGTr7wPgPGdmA4&start=10&sa=N&ved=2ahUKEwismc66kvfzAhXRyYsBHYB4B-MQ8tMDegQIARA0&biw=1800&bih=993&dpr=1.6
https://www.zhihu.com/zvideo/1350812665179152384
https://www.zhihu.com/question/424920760/answer/1520371371
https://www.zhihu.com/question/424920760/answer/1520371371
https://zhuanlan.zhihu.com/p/423934269
https://zhuanlan.zhihu.com/p/338356039
https://www.bilibili.com/video/BV1rK411N7M3
https://xargin.com/go-timer/
https://www.jianshu.com/p/427dfe8ad3c0
https://juejin.cn/post/6884914839308533774#heading-2
https://studygolang.com/articles/5224
https://mp.weixin.qq.com/s/QahprdKrlcaatG8poWsNrA
https://mp.weixin.qq.com/s/gaQhIo544VHYeGcKq34GIw
https://mp.weixin.qq.com/s/i8XWQgsgySIneZ6G_GPzuw
https://mp.weixin.qq.com/s/yYYgOfzwknh53yqBc3vL0Q
https://mp.weixin.qq.com/s/bV097lsHfdIaJFLYjaYYFA
cuishuangtodo
Go中定时器实现原理及源码解析
1.14版本之前是使用 64 个最小堆,运行时创建的所有计时器都会加入到最小堆中,每个处理器(P)创建的计时器会由对应的最小堆维护
在Go 在1.14版本之后,移除了timersBucket,所有的计时器都以最小四叉堆的形式存储 P 中。(所以不需要加锁)。timer 不再使用 timerproc 异步任务来调度,而是改用调度循环或系统监控调度的时候进行触发执行,减少了线程之间上下文切换带来的性能损失,并且通过使用 netpoll 阻塞唤醒机制可以让 timer 更加及时的得到执行。
timer 的触发有两种:
从调度循环中直接触发;
另一种是Go语言的后台系统监控中会定时触发;
通过 timer 的 1.13版本以及1.14版本后的对比可以发现,即使是一个定时器 go 语言都做了相当多的优化工作。从原来的需要维护 64 个桶,然后每个桶里面跑异步任务,到现在的将 timer列表直接挂到了 P 上面,这不仅减少了上下文切换带来的性能损耗,也减少了在锁之间的争抢问题,通过这些优化后有了可以媲美时间轮的性能表现
Go语言中时间轮的实现
原文链接: https://dashen.tech/2010/03/17/Go-time-NewTicker-与定时器/
版权声明: 转载请注明出处.