Go Runtime: 锁的阻塞方式

188 天前
 GopherDaily

从极端简化的角度来看, 线程阻塞于锁的方式有两种:

runtime.lock2 恰好涉及了上述两种场景.

...
	// On uniprocessors, no point spinning.
	// On multiprocessors, spin for ACTIVE_SPIN attempts.
	spin := 0
	if ncpu > 1 {
		spin = active_spin
	}
	for {
		// Try for lock, spinning.
		for i := 0; i < spin; i++ {
			for l.key == mutex_unlocked {
				if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
					return
				}
			}
			procyield(active_spin_cnt)
		}

		// Try for lock, rescheduling.
		for i := 0; i < passive_spin; i++ {
			for l.key == mutex_unlocked {
				if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
					return
				}
			}
			osyield()
		}

		// Sleep.
		v = atomic.Xchg(key32(&l.key), mutex_sleeping)
		if v == mutex_unlocked {
			return
		}
		wait = mutex_sleeping
		futexsleep(key32(&l.key), mutex_sleeping, -1)
	}
...

在多核的情况下, lock2 在需要阻塞时会优先尝试进行 4 次自旋. 自旋的实现和 CPU 有关, amd64 下会使用到 pause 这个指令.

procyield:

TEXT runtime·procyield(SB),NOSPLIT,$0-0
	MOVL	cycles+0(FP), AX
again:
	PAUSE
	SUBL	$1, AX
	JNZ	again
	RET

osyield 对应系统调用 sched_yeild 仅让出 CPU 使用权, 但线程依然在待执行队列等待调度.

futexsleep 对应睡眠, 底层系统调用是 futex. 调用成功后, 线程让出 CPU 使用权, 并变成等待状态, 直到 unlock2 中的 futexwake 被调用后, 线程才会重新放回待执行队列, 等到被调度后恢复执行.

Go 使用 GMP 做为调度模型, 所以在除了上述的自旋和睡眠外, 我们还可以选择仅挂起 G, 让线程(M) 去执行其他 G. 这样我们既可以避免线程级别的上下文切换成本, 又可以避免无意义的占用 CPU.

案例可以参考 semacquire1. goparkunlock 在释放 root.lock 之后, 将对应 G 的状态修改为 _Gwaiting, 并允许对应 M 执行其他 G.

...
	for {
		lockWithRank(&root.lock, lockRankRoot)
		// Add ourselves to nwait to disable "easy case" in semrelease.
		root.nwait.Add(1)
		// Check cansemacquire to avoid missed wakeup.
		if cansemacquire(addr) {
			root.nwait.Add(-1)
			unlock(&root.lock)
			break
		}
		// Any semrelease after the cansemacquire knows we're waiting
		// (we set nwait above), so go to sleep.
		root.queue(addr, s, lifo)
		goparkunlock(&root.lock, reason, traceBlockSync, 4+skipframes)
		if s.ticket != 0 || cansemacquire(addr) {
			break
		}
	}
...

Link: https://github.com/j2gg0s/j2gg0s/tree/main/_posts

777 次点击
所在节点    Go 编程语言
1 条回复
GopherDaily
188 天前
好像从 v2 骗了波流量,难得啊

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/987931

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX