上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.5.6 内核函数
内核使用的一些常用函数。
gopark, goready
休眠函数 gopark 解除当前 G 和 MP 的绑定,让 MP 重新去执行其他任务。
重点是 当前 G 并没有放回任务队列,除非直接唤醒,否则再不会被调度执行。
// proc.go // Puts the current goroutine into a waiting state and calls unlockf. // If unlockf returns false, the goroutine is resumed. func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, ...) { mp := acquirem() gp := mp.curg mp.waitlock = lock mp.waitunlockf = unlockf // mcall 会自动获取当前 G 做为 park_m 的调用参数。 // 同时会保存当前 G.sched 状态,详情参考本章内核调用。 mcall(park_m) }
// park continuation on g0. func park_m(gp *g) { // 这是 g0,参数 gp 是用户 G。 _g_ := getg() // 修改状态,解除 G(gp)、M 关联。 casgstatus(gp, _Grunning, _Gwaiting) dropg() // 调用 unlockf 函数。 // 如果失败,则继续执行 G(gp),表示休眠失败。 if fn := _g_.m.waitunlockf; fn != nil { ok := fn(gp, _g_.m.waitlock) _g_.m.waitunlockf = nil _g_.m.waitlock = nil if !ok { casgstatus(gp, _Gwaiting, _Grunnable) execute(gp, true) // Schedule it back, never returns. } } // 当前 MP 进入调度循环,执行其他任务。 schedule() }
func dropg() { // M.g0 _g_ := getg() // 透过 g0 访问 G 和 M,解除关联。 setMNoWB(&_g_.m.curg.m, nil) setGNoWB(&_g_.m.curg, nil) }
与之配套的是 goready 函数。
它负责将因 gopark 休眠的 G 放回队列,甚至可以优先使用 runnext。
func goready(gp *g, traceskip int) { systemstack(func() { ready(gp, traceskip, true) }) }
// Mark gp ready to run. func ready(gp *g, traceskip int, next bool) { // 修改状态,并重新放回队列。 casgstatus(gp, _Gwaiting, _Grunnable) runqput(_g_.m.p.ptr(), gp, next) // 尝试唤醒 MP 出来服务。 wakep() }
notesleep
像 linux、freebsd 基于 futex 实现,其他操作系统则使用信号量(semaphore)。
它并不解除 G、MP 关联,适合一些自旋(spanning)等待场合。
Futex 通常称作 “快速用户区互斥”,是一种在用户空间实现的锁(互斥)机制。
多执行单位(进程或线程)通过共享同一快内存(整数)来实现等待和唤醒操作。
因为 Futex 只在操作结果不一致时才进入内核仲裁,所以有非常高的执行效率。
更多内容请参考 man 2 futex。
基于 futex 实现的 lock/unlock,参考《8. 其他》。
// runtime2.go type m struct { park note } type note struct { key uintptr }
围绕 note.key 值进行休眠和唤醒操作。
// lock_futex.go func notesleep(n *note) { ns := int64(-1) for atomic.Load(key32(&n.key)) == 0 { futexsleep(key32(&n.key), 0, ns) // 如果 key == 0,休眠。(ns < 0,表示不超时) } // 直到被唤醒,跳出循环。 }
func notewakeup(n *note) { old := atomic.Xchg(key32(&n.key), 1) // 修改 key = 1。 futexwakeup(key32(&n.key), 1) // 唤醒 1 个等待。 }
func noteclear(n *note) { // 将 key 赋值为 0。 n.key = 0 }
通常情况下,notesleep 和 noteclear 组合使用。
1 表示唤醒,所以要重置为 0,否则下次 notesleep 失效。
notesleep(&_g_.m.park) // 被唤醒后 key = 1 noteclear(&_g_.m.park) // 重置 key = 0
notewakeup(&mp.park) // 设置 key = 1
futex
具体实现依赖操作系统提供。
注意,futexsleep & futexwakeup 都不会修改
*addr
值,需要自行处理。
// os_linux.go // Atomically, // if(*addr == val) sleep // Might be woken up spuriously; that's allowed. // Don't sleep longer than ns; ns < 0 means forever. func futexsleep(addr *uint32, val uint32, ns int64) { // 不超时。 if ns < 0 { futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, nil, nil, 0) return } var ts timespec ts.setNsec(ns) // 休眠,直到超时。 futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, unsafe.Pointer(&ts), nil, 0) }
// If any procs are sleeping on addr, wake up at most cnt. func futexwakeup(addr *uint32, cnt uint32) { // 唤醒 cnt 个等待。 ret := futex(unsafe.Pointer(addr), _FUTEX_WAKE_PRIVATE, cnt, nil, nil, 0) if ret >= 0 { return } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论