可重入锁的应用场景,用同一段代码的多次调用(递归调用),方法体之内有锁嵌套。golang官方并不推荐使用可重入锁。但一些面试官比较喜欢让人去手撕可重入锁的代码。一般是java面试官,因为在java中,锁的概念非常丰富, 在golang中相对来说还是比较简单且实用的

什么是可重入锁

可重入锁又称为递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法时会自动获取锁,不会因为之前已经获取过还没释放而阻塞。

举个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var mu = &sync.Mutex{}

func A() {
    mu.Lock()
    fmt.Println("A")
    B()
    mu.Unlock()
}

func B() {
    mu.Lock()
    fmt.Println("B")
    mu.Unlock()
}

以下的代码,在目前golang使用mutex的时候,会导致死锁的情况

1
fatal error: all goroutines are asleep - deadlock!

可重入锁的思路

  1. 记住持有锁的线程(golang中对应的是协程)
  2. 累计重入的次数

相关代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
type ReentrantMutex struct {
	mutex     sync.Mutex
	recursion int64 // 这个goroutine 重入的次数
	host      int64 // 当前持有锁的goroutine id
}

func GoRoutineID() int64 {
	var buf [64]byte
	var s = buf[:runtime.Stack(buf[:], false)]
	s = s[len("goroutine "):]
	s = s[:bytes.IndexByte(s, ' ')]
	gid, _ := strconv.ParseInt(string(s), 10, 64)
	return gid
}

func (m *ReentrantMutex) Lock() {
	host := GoRoutineID()
	fmt.Println(host)
	// 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入
	if atomic.LoadInt64(&m.host) == host {
		m.recursion++
		fmt.Println("fuck")
		return
	}
	m.mutex.Lock()
	atomic.StoreInt64(&m.host, host)
	m.recursion = 1
	fmt.Println(m)
}

func (m *ReentrantMutex) UnLock() {
	host := GoRoutineID()
	fmt.Printf("fuck:%v", m)
	if atomic.LoadInt64(&m.host) != host {
		panic("current goroutine not has lock")
	}
	m.recursion--
	if m.recursion > 0 { // 如果这个goroutine还没有完全释放,则直接返回
		return
	}
	// 此goroutine最后一次调用,需要释放锁
	atomic.StoreInt64(&m.host, 0)
	m.mutex.Unlock()
}

参考文档

https://cloud.tencent.com/developer/article/2045081 https://www.jianshu.com/p/440ad5d6e996