sync MutexRWMutexMutexRWMutexsynchronizedReentrantLock
(注:以下代码可以从 gitee 中克隆,https://gitee.com/elzatahmed/go-reentrant-lock)
1. 获取协程Id
GoLang 并没有像 Java 那样直接获取当前线程对象的方法,我们需要使用 Go Runtime 提供的 Stack 接口获取当前线程的栈信息,并从中抽取协程Id:
func getCurrentGoRoutineId() int64 {
var buf [64]byte
// 获取栈信息
n := runtime.Stack(buf[:], false)
// 抽取id
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine"))[0]
// 转为64位整数
id, _ := strconv.Atoi(idField)
return int64(id)
}
sync.Locker
sync.Locker
// A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
}
我们实现的可重入锁也需要实现该接口以更完美地融入 Go 的开发模式。
locker 结构体的大致形状如下代码所示:
type locker struct {}
func New() sync.Locker {
return &locker{}
}
func (l *locker) Lock() {}
func (l *locker) Unlock() {}
3. 实现 Lock 方法
为实现 Lock 方法,我们需要三种变量:
sync.Cond
// locker implements sync.Locker as reentrant locker,
// it can be reaquired multiple times by the same goroutine which is holding the locker
type locker struct {
heldCount int64 // how many times' locker has been held by the same goroutine
heldBy int64 // which goroutine is holding the locker
cond *sync.Cond // implements goroutine blocking and waking with sync.Cond
}
Lock 方法我们可以分为三种情况:
heldCount + 1heldByheldCount + 1
上述三种情况的判断我们都使用atomic.CompareAndSwap去执行。
// tryOwnerAcquire acquires the lock if and only if lock is currently held by same goroutine
func (l *locker) tryOwnerAcquire(gid int64) bool {
// CAS gid -> gid, 若成功则代表当前线程持有锁
if atomic.CompareAndSwapInt64(&l.heldBy, gid, gid) {
atomic.AddInt64(&l.heldCount, 1)
return true
}
return false
}
// tryNoneAcquire uses CAS to try to swap l.heldBy and l.heldCount field
func (l *locker) tryNoneAcquire(gid int64) bool {
// none = -1
// CAS none -> gid, 若成功则代表原本没有线程持有锁,当前线程获取到了锁
if atomic.CompareAndSwapInt64(&l.heldBy, none, gid) {
atomic.AddInt64(&l.heldCount, 1)
return true
}
return false
}
// acquireSlow waits for cond to signal
func (l *locker) acquireSlow(gid int64) {
l.cond.L.Lock()
// 若当前还是有其他线程持有锁
for atomic.LoadInt64(&l.heldBy) != none {
// 继续等待唤醒
l.cond.Wait()
}
// 若当前没有线程持有锁
l.acquireLocked(gid)
l.cond.L.Unlock()
}
// acquireLocked does acquire by current goroutine while locked
func (l *locker) acquireLocked(gid int64) {
// 由于sync.Cond的Signal方法每次只会唤醒一个线程,所以这里直接替换即可
atomic.SwapInt64(&l.heldBy, gid)
atomic.SwapInt64(&l.heldCount, 1)
}
实现好上述三种情况后,我们的 Lock 方法就如下代码所示:
// Lock locks the locker with reentrant mode
func (l *locker) Lock() {
// get current goroutine id
gid := getCurrentGoRoutineId()
// if current goroutine is the lock owner
if l.tryOwnerAcquire(gid) {
return
}
// if lock is not held by any goroutine
if l.tryNoneAcquire(gid) {
return
}
// if current goroutine is not the lock owner
l.acquireSlow(gid)
}
4. 实现 Unlock 方法
heldCount - 1heldCountcondSignal
// Unlock unlocks the locker using reentrant mode
func (l *locker) Unlock() {
// only the owner goroutine can enter this method
heldBy := atomic.LoadInt64(&l.heldBy)
if heldBy == none {
panic("unlock of unlocked reentrantLocker")
}
if heldBy == getCurrentGoRoutineId() {
l.releaseOnce()
return
}
panic("unlock by different goroutine")
}
// releaseOnce release once on locker
func (l *locker) releaseOnce() {
if l.heldCount == 0 {
panic("unlocks more than locks")
}
l.heldCount--
if l.heldCount == 0 {
l.heldBy = -1
// signal one and only one goroutine
l.cond.Signal()
}
return
}