Try Lock

1
2
3
4
5
6
7
8
9
10
11
12
type Mutex struct {...}
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock()
// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
func (m *Mutex) Unlock()

sync.Mutex 提供了 LockUnlock 两个方法分别对应上锁和解锁操作,如果锁已经被占据了,Lock 方法会阻塞相应的 goroutine 。在 C 中也有互斥锁的实现,但除了一般的上锁操作和解锁操作,它还多了一种方法 —— pthread_mutex_trylock ,也就是TryLockpthread_mutex_trylock 语义与 pthread_mutex_lock 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。

1
2
3
4
5
6
7
8
#include <pthread.h>
// lock a mutex, If the mutex is already locked,
// the calling thread will block until the mutex becomes available.
int pthread_mutex_lock(pthread_mutex_t *mutex);
// unlock a mutex
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// attempt to lock a mutex without blocking
int pthread_mutex_trylock(pthread_mutex_t *mutex);

下面我们自己来实现一个 golang 版本的 try lock 。实现互斥锁的关键就在于原子操作,保证即使有多个 CPU 存在的情况下,也不能同时执行同一条指令。利用 sync/atomic 库下面的方法可以轻松实现对一个数的原子操作。

我们实现的互斥锁中有两个东西,首先是一个 v int32 ,因为是互斥锁,所以初始化为1,还有一个 ch 通道用于唤醒阻塞等待的 goroutine

1
2
3
4
5
6
7
8
9
type Mutex struct {
v int32
ch chan struct{}
}

func (m *Mutex) Init() {
m.v = 1
m.ch = make(chan struct{}, 1)
}

Lock 方法获取一个锁,如果锁被占据了,则一直阻塞等待( <-m.ch ),等待其他 goroutine 唤醒,相当于 PV 操作中的 P

1
2
3
4
5
6
7
8
9
10
11
func (m *Mutex) Lock() {
if atomic.AddInt32(&m.v, -1) == 0 {
return
}
for {
if v := atomic.LoadInt32(&m.v); v >= 0 && atomic.SwapInt32(&m.v, -1) == 1 {
return
}
<-m.ch
}
}

Unlock 方法释放 Mutex ,如果当前有因为等待该锁而被阻塞的goroutine (说明 atomic.SwapInt32(&m.v, 1) != 0 ),则执行 m.ch <- struct{}{} 将阻塞的 goroutine 唤醒,对应 PV 操作中的 V

1
2
3
4
5
6
7
8
9
func (m *Mutex) Unlock() {
if atomic.SwapInt32(&m.v, 1) == 0 {
return
}
select {
case m.ch <- struct{}{}
default:
}
}

上面的 Lock 方法会在锁可用之前阻塞调用该方法的 goroutineTryLoad 方法并不会阻塞,通过 atomic.LoadInt32(&m.v) 来查看当前资源是否可用,如果不可用(说明 v <= 0 ),则返回 false ,如果 atomic.CompareAndSwapInt32(&m.v, 1, 0) 返回 true ,说明资源可用直接上锁。

1
2
3
4
5
6
7
func (m *Mutex) TryLock() bool {
v := atomic.LoadInt32(&m.v)
if v <= 0 {
return
}
return atomic.CompareAndSwapInt32(&m.v, 1, 0)
}
Pieces of Valuable Programming Knowledges