【Golang 面试题】每日 3 题(三十二)
✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
94. Go 互斥锁的底层实现结构
互斥锁对应的是底层结构是 sync.Mutex 结构体,,位于 src/sync/mutex.go 中
type Mutex struct {
state int32
sema uint32
}
state 表示锁的状态,有锁定、被唤醒、饥饿模式等,并且是用 state 的二进制位来标识的,不同模式下会有不同的处理方式。
sema 表示信号量,mutex 阻塞队列的定位是通过这个变量来实现的,从而实现 goroutine 的阻塞和唤醒。
addr = &sema
func semroot(addr *uint32) *semaRoot {
return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root
}
root := semroot(addr)
root.queue(addr, s, lifo)
root.dequeue(addr)
var semtable [251]struct {
root semaRoot
...
}
type semaRoot struct {
lock mutex
treap *sudog // root of balanced tree of unique waiters.
nwait uint32 // Number of waiters. Read w/o the lock.
}
type sudog struct {
g *g
next *sudog
prev *sudog
elem unsafe.Pointer // 指向sema变量
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
...
}
95. Go 互斥锁的操作
锁的实现一般会依赖于原子操作、信号量,通过 atomic 包中的一些原子操作来实现锁的锁定,通过信号量来实现线程的阻塞与唤醒。
加锁
通过原子操作 cas 加锁,如果加锁不成功,根据不同的场景选择自旋重试加锁或者阻塞等待被唤醒后加锁。
func (m *Mutex) Lock() {
// Fast path: 幸运之路,一下就获取到了锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
// Slow path:缓慢之路,尝试自旋或阻塞获取锁
m.lockSlow()
}
解锁
通过原子操作 add 解锁,如果仍有 goroutine 在等待,唤醒等待的 goroutine。
func (m *Mutex) Unlock() {
// Fast path: 幸运之路,解锁
new := atomic.AddInt32(&m.state, -mutexLocked)
if new != 0 {
// Slow path:如果有等待的goroutine,唤醒等待的goroutine
m.unlockSlow()
}}
96. Go 互斥锁的使用注意事项
- 在 Lock() 之前使用 Unlock() 会导致 panic 异常。
- 使用 Lock() 加锁后,再次 Lock() 会导致死锁(不支持重入),需 Unlock() 解锁后才能再加锁。
- 锁定状态与 goroutine 没有关联,一个 goroutine 可以 Lock,另一个 goroutine 可以 Unlock。