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))
    }
}

你可能感兴趣的:(golang)