在 Go 中对 Locks/Mutex 感到困惑

Getting confused on Locks/Mutex in Go

我正在尝试制作地图。通常所有读取都可以并行完成,除非写入时,所有读取都需要锁定。我以为我了解 Mutex 在 go 中的工作原理,但显然我不了解。

我第一次尝试使用 RWMutex 写锁:

type person struct {
    sync.RWMutex
    age int
}

func main() {
    a := person{age: 3}
    fmt.Println(a.age)
    go func() {
        a.Lock()
        time.Sleep(5 * time.Second)
        a.age = 4
        fmt.Println(a.age)
        a.Unlock()
    }()
    fmt.Println(a.age)
    fmt.Println("main", a.age)
    time.Sleep(20 * time.Second)
}

我有点期待写锁会锁读操作a.age。相反,我得到了:

3
3
main 3
4

所以我决定也添加一个读锁:

func main() {
    a := person{age: 3}
    fmt.Println(a.age)
    go func() {
        a.Lock()
        a.RLock()
        time.Sleep(5 * time.Second)
        a.age = 4
        fmt.Println(a.age)
        a.Unlock()
        a.RUnlock()
    }()
    fmt.Println(a.age)
    fmt.Println("main", a.age)
    time.Sleep(20 * time.Second)
}

更糟糕的是,我得到了:

3
3
main 3

显然我不明白这是如何工作的。感谢您的帮助。

从不double-lock。您的问题是您没有将 main 末尾的读取包装在锁中 - 如果他们不尝试建立锁,则没有什么可以阻止他们在其他东西写入时读取(即使写入正在使用锁)。锁本身就是提供互斥 (MutEx) 的东西,所以只有持续使用它才能获得它:

func main() {
    a := person{age: 3}
    fmt.Println(a.age)
    go func() {
        a.Lock()
        time.Sleep(5 * time.Second)
        a.age = 4
        fmt.Println(a.age)
        a.Unlock()
    }()
    a.RLock()
    fmt.Println(a.age)
    fmt.Println("main", a.age)
    a.RUnlock()
    time.Sleep(20 * time.Second)
}

这里没有魔法发生;实际上是对 LockRLock 的调用进行了锁定。如果您不调用它们,则不会阻止并发访问。当您调用 Lock 时,它会一直等到它可以完全锁定自己,然后锁定它和 returns。当您调用 RLock 时,它会一直等到没有写锁,然后获取一个(共享的)读锁。它正在调用那些提供互斥的函数,而不是幕后发生的任何魔法。

type person struct {
    sync.RWMutex
    age int
}

func main() {
    a := person{age: 3}
    fmt.Println(a.age)
    go func() {
        a.Lock()
        time.Sleep(5 * time.Second)
        a.age = 4
        fmt.Println(a.age)
        a.Unlock()
    }()
    fmt.Println(a.age)
    fmt.Println("main", a.age)
    time.Sleep(20 * time.Second)
}


3 <- 2nd line of `main` fmt.Println(a.age) 
3 <- after go routine fmt.Println(a.age)
main 3 <- fmt.Println("main", a.age)
4 <- goroutine after sleep fmt.Println(a.age)

写锁不会锁定变量并可能导致竞争条件。 (https://blog.golang.org/race-detector)

Locks 只会同步访问 a.age,从而使写入访问一次独占到单个 goroutine。它不会同步您的 go 例程,这将需要某种额外的同步。同步两者的最常见模式之一是使用等待组:

https://golang.org/pkg/sync/#WaitGroup

 func main() {
        var wg sync.WaitGroup
        wg.Add(1)
        a := person{age: 3}
        fmt.Println(a.age)
        go func() {
            defer wg.Done()
            a.Lock()
            time.Sleep(5 * time.Second)
            a.age = 4
            fmt.Println(a.age)
            a.Unlock()
        }()
        wg.Wait()
        fmt.Println(a.age)
    }

等待组同步两个 goroutine 以确保最后打印为 4