【发布时间】:2018-11-22 09:36:28
【问题描述】:
Golang 中的 RLock() 和 Lock() 有什么区别,我们在使用互斥锁时如何有效地使用它们?
【问题讨论】:
Golang 中的 RLock() 和 Lock() 有什么区别,我们在使用互斥锁时如何有效地使用它们?
【问题讨论】:
Lock():通过获取锁一次只能读取/写入一个 goroutine。
RLock():多个 goroutine 可以通过获取锁一次读取(而不是写入)。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
a := 0
lock := sync.RWMutex{}
for i := 1; i < 10; i++ {
go func(i int) {
lock.Lock()
fmt.Printf("Lock: from go routine %d: a = %d\n",i, a)
time.Sleep(time.Second)
lock.Unlock()
}(i)
}
b := 0
for i := 11; i < 20; i++ {
go func(i int) {
lock.RLock()
fmt.Printf("RLock: from go routine %d: b = %d\n",i, b)
time.Sleep(time.Second)
lock.RUnlock()
}(i)
}
<-time.After(time.Second*10)
}
1) 当一个 go-routine 已经获取了 RLock() 时,另一个 go-routine 是否可以获取 Lock() 用于写入,或者它必须等到 RUnlock() 发生?
2) 当有人已经为 map 获取 Lock() 时会发生什么,其他 go-routine 是否仍然可以获取 RLock()
3) 假设我们这里处理的是 Maps,有没有可能出现“并发读/写 Maps”错误?
请参阅以下示例以获得更多说明:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
lock := sync.RWMutex{}
b := map[string]int{}
b["0"] = 0
go func(i int) {
lock.RLock()
fmt.Printf("RLock: from go routine %d: b = %d\n",i, b["0"])
time.Sleep(time.Second*3)
fmt.Printf("RLock: from go routine %d: lock released\n",i)
lock.RUnlock()
}(1)
go func(i int) {
lock.Lock()
b["2"] = i
fmt.Printf("Lock: from go routine %d: b = %d\n",i, b["2"])
time.Sleep(time.Second*3)
fmt.Printf("Lock: from go routine %d: lock released\n",i)
lock.Unlock()
}(2)
<-time.After(time.Second*8)
fmt.Println("*************************************8")
go func(i int) {
lock.Lock()
b["3"] = i
fmt.Printf("Lock: from go routine %d: b = %d\n",i, b["3"])
time.Sleep(time.Second*3)
fmt.Printf("Lock: from go routine %d: lock released\n",i)
lock.Unlock()
}(3)
go func(i int) {
lock.RLock()
fmt.Printf("RLock: from go routine %d: b = %d\n",i, b["3"])
time.Sleep(time.Second*3)
fmt.Printf("RLock: from go routine %d: lock released\n",i)
lock.RUnlock()
}(4)
<-time.After(time.Second*8)
}
【讨论】:
RWMutex 是读写器互斥锁。锁可以由任意数量的读取器或单个写入器持有。 RWMutex 的零值是未锁定的互斥锁。
RWMutex 首次使用后不得复制。
如果一个 goroutine 持有一个用于读取的 RWMutex,而另一个 goroutine 可能会调用 Lock,那么在初始读取锁被释放之前,任何 goroutine 都不应该期望能够获取读取锁。特别是,这禁止递归读锁定。这是为了确保锁最终可用;阻塞的 Lock 调用会阻止新的读者获取锁。
互斥锁是互斥锁。互斥锁的零值是未锁定的互斥锁。
golang 提供的通道是并发控制的最佳实践,所以我认为使用 sync.lock 的有效方式并没有使用它,而是使用通道。
【讨论】: