1

Golang TryLock

 2 years ago
source link: https://ray-g.github.io/golang/golang_trylock/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Golang中的锁有sync包中的MutexRWMutex。然而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
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK