futex
futex(&f, FUTEX_WAIT, val, t, nil, 0)
选项FUTEX_WAIT,表示使用mmap在内核态和用户态之间共享f的内存,测试f的值如果等于val则休眠时间t
futex(&f, FUTEX_WAKE, cnt, nil, nil, 0)
选项FUTEX_WAKE,表示唤醒阻塞在f上的cnt个线程
--------------------------------------------------------------------------------
note
type note struct { key uintptr }
func notesleep(n *note) {
gp := getg()
if gp != gp.m.g0 { throw("notesleep not on g0") }
ns := int64(-1)
for atomic.Load(key32(&n.key)) == 0 { // 用户态测试,不需要陷入内核
gp.m.blocked = true
futexsleep(key32(&n.key), 0, ns) // 内核态测试并休眠
gp.m.blocked = false
}
}
func notewakeup(n *note) {
old := atomic.Xchg(key32(&n.key), 1)
if old != 0 { throw("notewakeup - double wakeup") }
futexwakeup(key32(&n.key), 1)
}
--------------------------------------------------------------------------------
mutex
type mutex struct { key uintptr }
const (
mutex_unlocked = 0
mutex_locked = 1
mutex_sleeping = 2
active_spin = 4
passive_spin = 1
)
func lock(l *mutex) { // 省略大量无关代码
v := atomic.Xchg(key32(&l.key), mutex_locked)
if v == mutex_unlocked { return } // 未上锁 -> 上锁
wait := v
for {
for i := 0; i < spin; i++ { // 尝试加锁, spinning
for l.key == mutex_unlocked {
if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
return
}
}
procyield(active_spin_cnt) // 30次PAUSE指令
}
v = atomic.Xchg(key32(&l.key), mutex_sleeping)
if v == mutex_unlocked { return }
wait = mutex_sleeping
futexsleep(key32(&l.key), mutex_sleeping, -1)
}
}
func unlock(l *mutex) {
v := atomic.Xchg(key32(&l.key), mutex_unlocked)
if v == mutex_sleeping { futexwakeup(key32(&l.key), 1) }
}
--------------------------------------------------------------------------------
semaphore
type semaRoot struct {
lock mutex
treap *sudog // treap的根
nwait uint32 // Number of waiters
}
const semTabSize = 251
var semtable [semTabSize]struct { root semaRoot } // 数组,每个元素都是treap的根
func semroot(addr *uint32) *semaRoot { // 通过sema的地址,找到对应的treap
return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root
}
type sudog struct {
g *g
isSelect bool // 是否参与select
next *sudog // treap的右孩子
prev *sudog // treap的左孩子
elem unsafe.Pointer // sema的地址addr
acquiretime int64
releasetime int64
ticket uint32 // treap随机值
parent *sudog // treap的parent
waitlink *sudog // 相同地址链表的下一个结点
waittail *sudog // 相同地址链表的尾结点
c *hchan // channel
}
sudog也是先从p的本地缓存获取,本地没有则到全局拿一半过来,全局也没有则创建,本地太多了则把一半放到全局缓存里。
信号量按addr的哈希值被组织成251颗treap,
不同的addr满足二叉搜索树的性质,而随机生成的ticket满足小顶堆的性质
相同的addr通过waitlink链接成链表
semacquire 如果*addr不为0,则将addr减1,返回。否则将g包装成sudog,放到treap里 goparkunlock
semrelease 将*addr加1,然后从treap里取出一个sudog,将里面的g放到等待队列里 goready