golang底层 note mutex semaphone
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