Go使用RWMutex操作map出现重复写入问题及方案咨询
问题根因
你遇到的重复写入问题本质是读写锁释放后的判断存在竞态条件:两个goroutine都在持有读锁的阶段读到了空值,释放读锁后用已经过期的旧值做判断,此时都满足len(t) <= 0的条件进入if代码块,后续加写锁只能保证写入操作的互斥,没法阻止已经进入if块的goroutine执行写入逻辑,所以会出现两次写入的情况。
问题2:上述代码是不是Go多goroutine操作map的正确实现?
不是。
RWMutex能保证的只有持有锁期间的操作是线程安全的,你将「值是否存在」的判断逻辑放在了读锁释放之后,判断依据的旧值和map当前的实际状态已经没有一致性保障,虽然写入时加了Lock不会出现map并发写入的panic,但逻辑上存在竞态,不符合业务预期。
问题1:有没有方法保证仅一个goroutine执行写入操作?
可以用**双重检查锁定(Double-Checked Locking)**的方案解决,核心逻辑是拿到写锁之后,再次读取map的当前状态做二次判断,避免重复写入:
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() // 加写锁后二次检查,确认当前map确实没有对应值再写入 t = r.mapEx[value] if len(t) <= 0 { fmt.Printf("%sLock\n", goroutine) r.mapEx[value] = value } r.mu.Unlock() fmt.Printf("%sUnlock\n", goroutine) if len(t) <= 0 { return value } } return t }
这个写法保留了原实现读多写少场景下的性能优势,仅在读未命中时才会加写锁,二次检查保证只有第一个拿到写锁的goroutine会执行写入,后续进入if块的goroutine拿到写锁后会发现已经有值,直接跳过写入逻辑。
如果你的场景是全局仅初始化一次这个key的值,也可以用sync.Once实现,写法更简洁,天然保证执行次数唯一:
type RLockAndLockStruct struct { mu sync.RWMutex initOnce sync.Once mapEx map[string]string } func (r *RLockAndLockStruct) RLockAndLockTest(value string, goroutine string) string { r.mu.RLock() t := r.mapEx[value] r.mu.RUnlock() if len(t) > 0 { return t } // 保证匿名函数仅执行一次 r.initOnce.Do(func() { r.mu.Lock() defer r.mu.Unlock() // 兼容多key场景的二次检查,如果是单key固定初始化可以去掉 if len(r.mapEx[value]) <= 0 { r.mapEx[value] = value } }) // 读取写入后的值 r.mu.RLock() t = r.mapEx[value] r.mu.RUnlock() return t }
内容的提问来源于stack exchange,提问作者Eduardo Albuquerque
相关产品推荐
相关产品推荐

