java和Go在可重入锁上的对比
面试提到有关go是如何实现可重入锁的,都不太记得go有这个,记录下
-
可重入锁的概念:指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
-
java的可重入锁:Java直接展示结果,是可以运行的,类似的代码结构在golang中会出现 竞争异常(java的synchronized也是可重入结构)
-
go的锁是否是可重入的
type Reentrant struct { sync.Mutex } func (receiver *Reentrant) methodA() { receiver.Lock() fmt.Println("method A is running") receiver.methodB() receiver.Unlock() } func (receiver Reentrant) methodB() { receiver.Lock() fmt.Println("method B is running") receiver.Unlock() } func TestReentrant(t *testing.T) { a := new(Reentrant) a.methodA() }
-
go实习一个简单的可重入锁
关于可重入锁的原理,需要存储的信息包括锁定状态(需要注意状态设置的原子性),持有锁的线程,以及重入的次数,其中针对go来说获取线程编号存在问题import ( "fmt" "runtime" "strconv" "strings" "sync" "sync/atomic" ) type MyReentrantLock struct { lock sync.Mutex id int counter int32 } func (l *MyReentrantLock) Lock() { // 第一次锁 if atomic.CompareAndSwapInt32(&(l.counter), 0, 1){ l.lock.Lock() l.id = GoID() }else { if GoID() == l.id{ atomic.AddInt32(&(l.counter), 1) }else { // 这里就是阻塞goroutine了 l.lock.Lock() } } } func (l *MyReentrantLock) Unlock() { // 当counter归零是真正释放锁 v := atomic.LoadInt32(&(l.counter)) if v == 0{ panic("this mutex is not locked") } if l.id == GoID(){ v := atomic.AddInt32(&(l.counter), -1) if v == 0 { l.lock.Unlock() } } } func GoID() int { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) } return id }