Golang TryLock
Golang中的锁有sync
包中的Mutex
和RWMutex
。然而Go中的锁实现的比较简单,在没有获取到锁的时候,会被阻塞住,一直等下去。
这样的锁的设计,有时候不容易满足现实中的需求,比如一个事务需要更新两个资源A和B,
我们在锁了A后需要锁B,然而恰好这个时候有人锁了B但是在等着锁A,这个时候Go的锁机制就会出现死锁了。
如果这个时候,我们不是用这种阻塞的锁,而是用一个名不付实的TryLock
就可以解决这个问题了。
很多年以前,有人就给Go提了Issue,要求添加TryLocksync: mutex.TryLock。 然而不幸的是这个请求一直没有被纳入官方库,到最后在官方清理的时候还给关掉了,意味着官方库不会添加这个需求了。 这个Issue中,Rob Pike还顺便吐槽了一下TryLock是如何的名不付实,返回true的时候明明已经真的锁了,名字却叫做尝试。
Unsafe 直接操作 Mutex
Mutex的结构如下:
// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}
mutexLocked = 1 << iota // mutex is locked
Mutex使用其中的state
字段来标记锁是否被占用,0是unlock, 1是locked。
所以,我们可以取巧的来直接操作state
,反正官方实现的quick path也是这样的。
const mutexLocked = 1 << iota // mutex is locked
type Mutex struct {
sync.Mutex
}
func (m *Mutex) TryLock() bool {
return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), 0, mutexLocked)
}
无休眠版 SpinLock
利用CAS来尝试获取锁,还可以创建一个自己的spinlock
type SpinLock struct {
state uint32
}
func (sl *SpinLock) Lock() {
for !sl.TryLock() {
runtime.Gosched()
}
}
func (sl *SpinLock) Unlock() {
atomic.StoreUint32(&sl.state, 0)
}
func (sl *SpinLock) TryLock() bool {
return atomic.CompareAndSwapUint32(&sl.state, 0, mutexLocked)
}
看起来,就是一个标准库的Mutex的精简版,只有quick path,在quick path走不通的时候,没有slow path中的休眠唤醒等。 这样的SpinLock在并发量很大的时候,肯定是CPU占用很高的,因为如果锁被别人占用很长的时间,这个Spin的循环会一直跑。 获取锁的速度可能很快,但是CPU占用率会比较高。
这个版本还可以优化到不要结构
type spinLock uint32
func (sl *spinLock) Lock() {
for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, mutexLocked) {
runtime.Gosched()
}
}
func (sl *spinLock) Unlock() {
atomic.StoreUint32((*uint32)(sl), 0)
}
func (sl *spinLock) TryLock() bool {
return atomic.CompareAndSwapUint32((*uint32)(sl), 0, mutexLocked)
}
func SpinLock() sync.Locker {
var lock spinLock
return &lock
}
使用 channel 做锁
使用无buffer的channel也可以做把锁,但是需要注意的是,这个Unlock如果连续两次调用的话,会把自己锁住。
type ChanMutex chan struct{}
func (m *ChanMutex) Lock() {
ch := (chan struct{})(*m)
ch <- struct{}{}
}
func (m *ChanMutex) Unlock() {
ch := (chan struct{})(*m)
select {
case <-ch:
default:
panic("unlock of unlocked mutex")
}
}
func (m *ChanMutex) TryLock() bool {
ch := (chan struct{})(*m)
select {
case ch <- struct{}{}:
return true
default:
}
return false
}
func NewLocker() *ChanMutex {
l := make(ChanMutex, 1)
return &l
}