乐趣区

关于golang:Go语言学习笔记读锁重入导致死锁

景象


func TestLock(t *testing.T) {l := &lockWrapper{}
    var wg sync.WaitGroup
    wg.Add(2)
    go func(){
        s := 0
        for i := 0 ; i < 0 ; 1000000; i++{s += l.Get()
        } 
        t.Log(s)
        wg.Done()}()
    
    go func() {
        for i := 0 ; i < 0 ; 1000000; i++{l.Set(i)
        } 
        t.Log(s)
        wg.Done()}()

    wg.Wait()}

type lockWrapper struct {
    mu sync.RWMutex
    a  int
}

func (l *lockWrapper) Get() int {l.mu.RLock()
    defer l.mu.RUnlock()
    
    //...
    l.mu.RLock()
    defer l.mu.RUnlock()

    return a
}

func (l *lockWrapper) Set(s int) {l.mu.Lock()
    defer l.mu.Unlock()
    l.a = s
}

上述代码可能导致死锁

剖析

在 Go 中,先说论断 Lock() 是优先于 RLock() 的,如果在一个协程中重入同一个 RLock 而另一个协程并行地调用Lock,这种状况下就会造成死锁

seq g0 g1
0 RLock
1 Lock
2 RLock

在执行 Lock 时

func (rw *RWMutex) Lock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()}
    // First, resolve competition with other writers.
    rw.w.Lock()
    // Announce to readers there is a pending writer.
    // ! 此处会将标记为置为正数
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    // Wait for active readers.
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {runtime_SemacquireMutex(&rw.writerSem, false, 0)
    }
    if race.Enabled {race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
        race.Acquire(unsafe.Pointer(&rw.writerSem))
    }
}

而执行 RLock 则会因为 Lock 批改了标记位而陷入期待,造成死锁

func (rw *RWMutex) RLock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()}
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // A writer is pending, wait for it.
        runtime_SemacquireMutex(&rw.readerSem, false, 0)
    }
    if race.Enabled {race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
    }
}
退出移动版