- // Go 语言中暴露的 semaphore 实现
- // 具体的用法是提供 sleep 和 wakeup 原语
- // 以使其能够在其它同步原语中的竞争情况下使用
- // 因此这里的 semaphore 和 Linux 中的 futex 目标是一致的
- // 只不过语义上更简单一些
- //
- // 也就是说,不要认为这些是信号量
- // 把这里的东西看作 sleep 和 wakeup 实现的一种方式
- // 每一个 sleep 都会和一个 wakeup 配对
- // 即使在发生 race 时,wakeup 在 sleep 之前时也是如此
- //
- // See Mullender and Cox, “Semaphores in Plan 9,''
- // http://swtch.com/semaphore.pdf
- // 为 sync.Mutex 准备的异步信号量
- // semaRoot 持有一棵 地址各不相同的 sudog(s.elem) 的平衡树
- // 每一个 sudog 都反过来指向(通过 s.waitlink)一个在同一个地址上等待的其它 sudog 们
- // 同一地址的 sudog 的内部列表上的操作时间复杂度都是 O(1)。顶层 semaRoot 列表的扫描
- // 的时间复杂度是 O(log n),n 是被哈希到同一个 semaRoot 的不同地址的总数,每一个地址上都会有一些 goroutine 被阻塞。
- // 访问 golang.org/issue/17953 来查看一个在引入二级列表之前性能较差的程序样例,test/locklinear.go
- // 中有一个复现这个样例的测试
- type semaRoot struct {
- lock mutex
- treap *sudog // root of balanced tree of unique waiters.
- nwait uint32 // Number of waiters. Read w/o the lock.
- }
- // Prime to not correlate with any user patterns.
- const semTabSize = 251
- var semtable [semTabSize]struct {
- root semaRoot
- pad [sys.CacheLineSize – unsafe.Sizeof(semaRoot{})]byte
- }
- func semroot(addr *uint32) *semaRoot {
- return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root
- }
- ┌─────┬─────┬─────┬─────┬─────┬────────────────────────┬─────┐
- │ 0 │ 1 │ 2 │ 3 │ 4 │ ….. │ 250 │
- └─────┴─────┴─────┴─────┴─────┴────────────────────────┴─────┘
- │ │
- │ │
- └──┐ └─┐
- │ │
- │ │
- ▼ ▼
- ┌─────────┐ ┌─────────┐
- │ struct │ │ struct │
- ├─────────┴─────────┐ ├─────────┴─────────┐
- │ root semaRoot │──┐ │ root semaRoot │──┐
- ├───────────────────┤ │ ├───────────────────┤ │
- │ pad │ │ │ pad │ │
- └───────────────────┘ │ └───────────────────┘ │
- │ │
- ┌────────────────┘ ┌────────────────┘
- │ │
- │ │
- ▼ ▼
- ┌──────────┐ ┌──────────┐
- │ semaRoot │ │ semaRoot │
- ├──────────┴────────┐ ├──────────┴────────┐
- │ lock mutex │ │ lock mutex │
- ├───────────────────┤ ├───────────────────┤
- │ treap *sudog │ │ treap *sudog │
- ├───────────────────┤ ├───────────────────┤
- │ nwait uint32 │ │ nwait uint32 │
- └───────────────────┘ └───────────────────┘
treap 结构:
- ┌──────────┐
- ┌─┬─▶│ sudog │
- │ │ ├──────────┴────────────┐
- ┌─────────────────────┼─┼──│ prev *sudog │
- │ │ │ ├───────────────────────┤
- │ │ │ │ next *sudog │────┐
- │ │ │ ├───────────────────────┤ │
- │ │ │ │ parent *sudog │ │
- │ │ │ ├───────────────────────┤ │
- │ │ │ │ elem unsafe.Pointer │ │
- │ │ │ ├───────────────────────┤ │
- │ │ │ │ ticket uint32 │ │
- │ │ │ └───────────────────────┘ │
- │ │ │ │
- │ │ │ │
- │ │ │ │
- │ │ │ │
- │ │ │ │
- │ │ │ │
- ▼ │ │ ▼
- ┌──────────┐ │ │ ┌──────────┐
- │ sudog │ │ │ │ sudog │
- ├──────────┴────────────┐ │ │ ├──────────┴────────────┐
- │ prev *sudog │ │ │ │ prev *sudog │
- ├───────────────────────┤ │ │ ├───────────────────────┤
- │ next *sudog │ │ │ │ next *sudog │
- ├───────────────────────┤ │ │ ├───────────────────────┤
- │ parent *sudog │───┘ └─────────────────────────│ parent *sudog │
- ├───────────────────────┤ ├───────────────────────┤
- │ elem unsafe.Pointer │ │ elem unsafe.Pointer │
- ├───────────────────────┤ ├───────────────────────┤
- │ ticket uint32 │ │ ticket uint32 │
- └───────────────────────┘ └───────────────────────┘
在这个 treap 结构里,从 elem 的视角(其实就是 lock 的 addr)来看,这个结构是个二叉搜索树。从 ticket 的角度来看,整个结构就是一个小顶堆。
所以才叫树堆(treap)。
相同 addr,即对同一个 mutex 上锁的 g,会阻塞在同一个地址上。这些阻塞在同一个地址上的 goroutine 会被打包成 sudog,组成一个链表。用 sudog 的 waitlink 相连:
- ┌──────────┐ ┌──────────┐ ┌──────────┐
- │ sudog │ ┌─────▶│ sudog │ ┌─────▶│ sudog │
- ├──────────┴────────────┐ │ ├──────────┴────────────┐ │ ├──────────┴────────────┐
- │ waitlink *sudog │─────┘ │ waitlink *sudog │──────┘ │ waitlink *sudog │
- ├───────────────────────┤ ├───────────────────────┤ ├───────────────────────┤
- │ waittail *sudog │ │ waittail *sudog │ │ waittail *sudog │
- └───────────────────────┘ └───────────────────────┘ └───────────────────────┘
中间的元素的 waittail 都会指向最后一个元素:
- ┌──────────┐
- │ sudog │
- ├──────────┴────────────┐
- │ waitlink *sudog │
- ├───────────────────────┤
- │ waittail *sudog │───────────────────────────────────────────────────────────┐
- └───────────────────────┘ │
- ┌──────────┐ │
- │ sudog │ │
- ├──────────┴────────────┐ │
- │ waitlink *sudog │ │
- ├───────────────────────┤ │
- │ waittail *sudog │─────────────────────────┤
- └───────────────────────┘ ▼
- ┌──────────┐
- │ sudog │
- ├──────────┴────────────┐
- │ waitlink *sudog │
- ├───────────────────────┤
- │ waittail *sudog │
- └───────────────────────┘
对外封装
在 sema.go 里实现的内容,用 go:linkname 导出给 sync、poll 库来使用,也是在链接期做了些手脚:
- //go:linkname sync_runtime_Semacquire sync.runtime_Semacquire
- func sync_runtime_Semacquire(addr *uint32) {
- semacquire1(addr, false, semaBlockProfile)
- }
- //go:linkname poll_runtime_Semacquire internal/poll.runtime_Semacquire
- func poll_runtime_Semacquire(addr *uint32) {
- semacquire1(addr, false, semaBlockProfile)
- }
- //go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
- func sync_runtime_Semrelease(addr *uint32, handoff bool) {
- semrelease1(addr, handoff)
- }
- //go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
- func sync_runtime_SemacquireMutex(addr *uint32, lifo bool) {
- semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile)
- }
- //go:linkname poll_runtime_Semrelease internal/poll.runtime_Semrelease
- func poll_runtime_Semrelease(addr *uint32) {
- semrelease(addr)
- }