在 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)
}
这里没有魔法发生;实际上是对 Lock
和 RLock
的调用进行了锁定。如果您不调用它们,则不会阻止并发访问。当您调用 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
我正在尝试制作地图。通常所有读取都可以并行完成,除非写入时,所有读取都需要锁定。我以为我了解 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)
}
这里没有魔法发生;实际上是对 Lock
和 RLock
的调用进行了锁定。如果您不调用它们,则不会阻止并发访问。当您调用 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