可重入锁的应用场景,用同一段代码的多次调用(递归调用),方法体之内有锁嵌套。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!
|
可重入锁的思路#
- 记住持有锁的线程(golang中对应的是协程)
- 累计重入的次数
相关代码#
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