英文:
Using Lock and Rlock in the same go routine
问题
我正在使用Go语言中的RWMutex进行实验,并意识到使用以下代码可能会出现这种行为:
- goroutine 1 - RLock
- goroutine 1 - RUnlock
- goroutine 2 - RLock
- goroutine 2 - RUnlock
- goroutine 2 - Lock
- goroutine 2 - Unlock
- goroutine 1 - Lock
- goroutine 1 - Unlock
package main
import (
"fmt"
"sync"
"time"
)
type RLockAndLockStruct struct {
mu sync.RWMutex
mapEx map[string]string
}
func main() {
r := &RLockAndLockStruct{}
r.mapEx = make(map[string]string)
go r.RLockAndLockTest("test", "goroutine 1 - ")
go r.RLockAndLockTest("test", "goroutine 2 - ")
time.Sleep(4000 * time.Millisecond)
}
func (r *RLockAndLockStruct) RLockAndLockTest(value string, goroutine string) string {
r.mu.RLock()
fmt.Printf("%sRLock\n", goroutine)
t := r.mapEx[value]
r.mu.RUnlock()
fmt.Printf("%sRUnlock\n", goroutine)
if len(t) <= 0 {
time.Sleep(500 * time.Millisecond)
r.mu.Lock()
fmt.Printf("%sLock\n", goroutine)
r.mapEx[value] = value
r.mu.Unlock()
fmt.Printf("%sUnlock\n", goroutine)
return r.mapEx[value]
}
return t
}
我在一些文章中读到,使用RWMutex的RLock进行读取,使用Lock进行写入是使用Goroutines的map的正确方式。然而,如上面的代码所示,如果两个Goroutines几乎同时开始,可能会出现两次写入map而不是一次写入和一次读取。
基于此,我有一些问题:
- 是否有办法确保只有一个Goroutine在map上写入(进入上面的if代码块),而所有其他例程都使用新值读取该map(避免进入if代码块)?
- 这是正确的Goroutine和map实现吗?
英文:
I am working in an experiment using RWMutex in Go, and I realized that is possible to have this behavior with the follow code:
- goroutine 1 - RLock
- goroutine 1 - RUnlock
- goroutine 2 - RLock
- goroutine 2 - RUnlock
- goroutine 2 - Lock
- goroutine 2 - Unlock
- goroutine 1 - Lock
- goroutine 1 - Unlock
package main
import (
"fmt"
"sync"
"time"
)
type RLockAndLockStruct struct {
mu sync.RWMutex
mapEx map[string]string
}
func main() {
r := &RLockAndLockStruct{}
r.mapEx = make(map[string]string)
go r.RLockAndLockTest("test", "goroutine 1 - ")
go r.RLockAndLockTest("test", "goroutine 2 - ")
time.Sleep(4000 * time.Millisecond)
}
func (r *RLockAndLockStruct) RLockAndLockTest(value string, goroutine string) string {
r.mu.RLock()
fmt.Printf("%sRLock\n", goroutine)
t := r.mapEx[value]
r.mu.RUnlock()
fmt.Printf("%sRUnlock\n", goroutine)
if len(t) <= 0 {
time.Sleep(500 * time.Millisecond)
r.mu.Lock()
fmt.Printf("%sLock\n", goroutine)
r.mapEx[value] = value
r.mu.Unlock()
fmt.Printf("%sUnlock\n", goroutine)
return r.mapEx[value]
}
return t
}
I have read in some articles that the correct way to use map in Goroutines is using RWMutex with RLock to read and Lock to write. However, as you can see in the code above, if two Goroutines starts almost in the same time, it is possible to have two writes in the same map instead of one write and one read.
From that, I have some questions here:
- Is there a way that we can guarantee just one goroutine writing on a map (entering the if codeblock above) and all other routines reading that map with the new value (avoid entering the if codeblock)?
- Is it the correct implementation for goroutine and maps?
答案1
得分: 7
这是因为你的代码存在竞态条件。你在读取地图时使用了读锁,做出决策后再使用写锁。但不能保证在获取写锁时,你做出决策的条件仍然成立。
正确的方法是在加锁后重新测试条件:
if len(t) <= 0 {
time.Sleep(500 * time.Millisecond)
r.mu.Lock()
if len(r.mapEx[value]) <= 0 {
fmt.Printf("%sLock\n", goroutine)
r.mapEx[value] = value
}
r.mu.Unlock()
return value
请注意上面使用了return value
,否则就需要再次访问地图。
英文:
This is because your code has a race condition in it. You read-lock the map to read it, make a decision, and then write-lock it. There is no guarantee that when you get the write lock, the condition you made the decision on still holds.
The correct way would be to retest the condition after locking:
if len(t) <= 0 {
time.Sleep(500 * time.Millisecond)
r.mu.Lock()
if len(r.mapEx[value])<=0 {
fmt.Printf("%sLock\n", goroutine)
r.mapEx[value] = value
}
r.mu.Unlock()
return value
Note the use of return value
above, because otherwise it would have to access the map again.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论