在看 golang sync.Mutex 锁时看到调用 sema 来实现 gorutine 的锁定,而 sema 调用 lock_sema 对 m 进行 park 和唤醒,就对 lock_sema 的源码了解了一波
lock
先从寄存器或者 TLS 里获取当前 g,继而获取 m,cas 抢占锁,cas 成功,说明抢到锁,直接返回。
每个 m 都有自己的 pthread_mutex 和 pthread_cond ,用来给自己加锁解锁等待和唤醒。
当 cas 失败,会空转自旋 4 次,四次都没有获取到锁,第五次会睡眠一毫秒,让出 cpu 执行时间,防止一直占用 cpu 用来空转
上面五次操作进行完,还是没有获取到锁,就会加入队列,并且调用 m 的 pthread_mutex 和 pthread_cond 把 m 挂起并且等待,队列是先入后出。
直到被唤醒,因为加入到队列时,存储的是 m 的指针,所以当前 m 唤醒队列中的 m 时,直接通过指针获取到 m, 同样调用 m 的 pthread_cond 把 m 唤醒和解锁,m 再去竞争锁
lock_sema 不支持重入,所以不能在同一 g 中重复加锁
unlock
解锁就很简单,不解释了,下面直接贴注释过的代码
代码
func lock(l *mutex) {
gp := getg()
if gp.m.locks < 0 {
throw("runtime·lock: lock count")
}
gp.m.locks++
// Speculative grab for lock.
// 直接 cas 就锁上了
if atomic.Casuintptr(&l.key, 0, locked) {
return
}
semacreate(gp.m)
// On uniprocessor's, no point spinning.
// On multiprocessors, spin for ACTIVE_SPIN attempts.
// 在单处理器上没有意义,多处理器上,自旋最多 ACTIVE_SPIN
spin := 0
if ncpu > 1 {
spin = active_spin
}
Loop:
for i := 0; ; i++ {
v := atomic.Loaduintptr(&l.key)
// 锁释放了
if v&locked == 0 {
// Unlocked. Try to lock.
if atomic.Casuintptr(&l.key, v, v|locked) {
return
}
i = 0
}
if i < spin {
// 空转三十次
procyield(active_spin_cnt)
} else if i < spin+passive_spin {
// 让出CPU,实现是睡眠一微秒
osyield()
} else {
// Someone else has it.
// l->waitm points to a linked list of M's waiting
// for this lock, chained through m->nextwaitm.
// Queue this M.
for {
// 指向占有锁的线程 m
gp.m.nextwaitm = muintptr(v &^ locked)
// cas 设置锁标志,只是为了让确定顺序,让下一个线程指向自己,进队列,先进后出
if atomic.Casuintptr(&l.key, v, uintptr(unsafe.Pointer(gp.m))|locked) {
break
}
// cas 不成功说明锁标志被改了,有可能被别的线程占了,有可能释放了,重新获取锁标志
v = atomic.Loaduintptr(&l.key)
// 锁被释放了,继续循环
if v&locked == 0 {
continue Loop
}
}
// 被锁住了
if v&locked != 0 {
// Queued. Wait.
semasleep(-1)
// 被唤醒之后, 继续抢锁
i = 0
}
}
}
}
//go:nowritebarrier
// We might not be holding a p in this code.
func unlock(l *mutex) {
gp := getg()
var mp *m
for {
v := atomic.Loaduintptr(&l.key)
// 只有当前线程竞争
if v == locked {
if atomic.Casuintptr(&l.key, locked, 0) {
break
}
} else {
// Other M's are waiting for the lock.
// Dequeue an M.
// 获取标志位中存储的 m
mp = muintptr(v &^ locked).ptr()
// 出队列
if atomic.Casuintptr(&l.key, v, uintptr(mp.nextwaitm)) {
// Dequeued an M. Wake it.
semawakeup(mp)
break
}
}
}
gp.m.locks--
if gp.m.locks < 0 {
throw("runtime·unlock: lock count")
}
if gp.m.locks == 0 && gp.preempt { // restore the preemption request in case we've cleared it in newstack 恢复抢占请求,以防我们在新堆栈中清除了它
gp.stackguard0 = stackPreempt
}
}
//go:nosplit
func semasleep(ns int64) int32 {
var start int64
if ns >= 0 {
start = nanotime()
}
mp := getg().m
// 互斥锁锁定
pthread_mutex_lock(&mp.mutex)
for {
if mp.count > 0 {
mp.count--
pthread_mutex_unlock(&mp.mutex)
return 0
}
if ns >= 0 {
spent := nanotime() - start
if spent >= ns {
pthread_mutex_unlock(&mp.mutex)
return -1
}
var t timespec
t.setNsec(ns - spent)
err := pthread_cond_timedwait_relative_np(&mp.cond, &mp.mutex, &t)
if err == _ETIMEDOUT {
pthread_mutex_unlock(&mp.mutex)
return -1
}
} else {
// 被唤醒之后继续 for 循环, 此时 mp.count 已经大于0了
pthread_cond_wait(&mp.cond, &mp.mutex)
}
}
}
//go:nosplit
func semawakeup(mp *m) {
pthread_mutex_lock(&mp.mutex)
mp.count++
if mp.count > 0 {
pthread_cond_signal(&mp.cond)
}
pthread_mutex_unlock(&mp.mutex)
}