golang基于Mutex实现可重入锁
锁重入的定义
锁可重入也就是当前已经获取到锁的goroutine继续调用Lock方法获取锁,Go标准库中提供了sync.Mutex实现了排他锁,但并不是可重入的,如果在代码中重入锁,也就是Lock之后再次进行Lock获取锁,则会被阻塞到第二次Lock上,锁没有办法得到释放从而影响其它goroutine执行
// 例如
package main;
import "sync"
func ReentryExample() {
var c int64
var mu sync.Mutex
mu.Lock() // 第一次加锁
// TODO //
mu.Lock() // 第二次加锁,阻塞
c++;
// TODO ...
}
重入锁的简单实现思路
- 拿到能够识别到当前协程的id,(通过堆栈信息获取到goroutine的id)
- 写一个结构体,实现Locker接口
首先获取到goroutine的id
func GoID() int64 {
var buf [32]byte
n := runtime.Stack(buf[:],false) // 获取堆栈的信息
// string(buf[:n]
/**
goroutine 6 [running]:
main.XXX
*/
// 拿到goroutine的id
goIdStr := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine"))[0]
goId, err := strconv.Atoi(fieldId)// 转换为int
return int64(goId)
}
然后开始编写可重入锁的结构体
// ReentrantMutex 可重入的互斥锁
type ReentrantMutex struct {
sync.Mutex // 互斥锁
goId int64 // 用于保存goroutine的id
recursion int64 // 锁重入的次数
}
// Lock 实现Locker接口,用于加锁
func (r *ReentrantMutex) Lock() {
gid := GoID()
if atomic.LoadInt64(&r.goId) == gid { // 看看是否已经加过锁了?
atomic.AddInt64(&r.recursion, 1) // 如果之前加过锁,则重入的次数+1
return
}
r.Mutex.Lock() // 使用互斥锁上锁
atomic.StoreInt64(&r.goId, gid) // 使用原子操作保存goroutine的id
atomic.StoreInt64(&r.recursion, 1) // 第一次加锁,因此重入的次数为一
}
// Unlock 实现了Locker的接口,用于解锁
func (r *ReentrantMutex) Unlock() {
gid := GoID()
if atomic.LoadInt64(&r.goId) != gid { // 看是否加过锁
panic("未加锁") // 没有加过锁,不存在解锁,直接panic
}
recursion := atomic.AddInt64(&r.recursion, -1) // 重入次数-1
if recursion != 0 { // 如果重入次数没有等于0(意味着还有锁没有释放)
return
}
atomic.StoreInt64(&r.goId, -1) // 重入次数为0,则不存在锁没有释放,解锁
r.Mutex.Unlock() // 互斥锁解锁
}
测试用例
package main;
func main() {
var m ReentrantMutex
m.Lock()
m.Lock() // 不会阻塞
fmt.Println("1") // 正常打印1
m.Unlock()
m.Unlock()// 解锁
}