Go语言sync.Map不同key的写入操作是否支持并发执行
Golang sync.Map 键级锁实现尝试
以下是基于sync.Map实现针对特定字符串(例如"LOCK1")的键级别加锁能力的示例代码:
package main import ( "fmt" "sync" "time" ) var lockMap sync.Map func main(){ counter := func(key string, routineId string) { _, ok := lockMap.Load(key) if ok { fmt.Println(key + " skipped by " + routineId) return } lockMap.Store(key, true) defer lockMap.Delete(key) for i := 0; i < 10; i++ { fmt.Println(key + " locked by " + routineId) } } // 启动多个协程测试 go counter("LOCK1", "routine1") go counter("LOCK1", "routine2") go counter("LOCK1", "routine3") go counter("LOCK1", "routine4") go counter("LOCK1", "routine5") // 等待协程执行完成 time.Sleep(time.Second) }
核心疑问
已知sync.Map底层基于读写锁(RWMutex)逻辑实现,需要明确其并发写入规则:
sync.Map是否允许同一时刻对不同key执行并发写入,还是同一时刻仅允许单个协程对整个Map执行写入操作?- 举例场景:若协程
routine1正在设置key"LOCK1"的值,协程routine2是否可以并发写入新key"LOCK2",还是必须等待前一个写入操作完成?
解答
首先纠正一个常见认知偏差:sync.Map并非简单通过全局RWMutex保护全量数据,它做了读写分离的性能优化,内部核心分为两个存储结构:无锁访问的只读read表、受全局互斥锁保护的dirty表,不同场景下的写入并发特性如下:
- 若写入的key已经存在于read表中:写入走无锁快路径,通过原子操作直接更新值,不需要持有全局锁,不同key的这类写入完全可以并发执行,不会互相阻塞
- 若写入的key不存在于read表中(比如写入从未添加过的全新key):写入需要先持有全局互斥锁,再操作dirty表完成写入,这一阶段全局锁仅被一个协程持有,其他需要加锁的写入操作会阻塞等待
- 对应你提到的场景:
- 若
"LOCK1"和"LOCK2"都已经存在于read表,routine1写"LOCK1"、routine2写"LOCK2"都走无锁路径,完全可以并发执行,不需要等待 - 若
"LOCK1"已存在于read表、"LOCK2"是全新key:routine1写"LOCK1"不需要持锁,routine2写"LOCK2"虽然需要加全局锁,但不会和routine1产生锁竞争,两者不需要互相等待 - 若
"LOCK1"和"LOCK2"都是从未写入过的全新key:两个协程写入时会竞争同一把全局锁,必须等前一个协程完成加锁、写入、释放锁的全流程,后一个协程才能继续执行。
- 若
注意:你提供的示例代码存在并发缺陷:
Load检查key是否存在和后续Store写入占位的操作不是原子的,多个协程可能同时通过Load拿到key不存在的结果,重复进入临界区,无法实现单key互斥的效果。要实现可靠的键级锁,建议将值设置为sync.Mutex指针,搭配LoadOrStore方法保证检查+写入的原子性。
内容的提问来源于stack exchange,提问作者DntFrgtDSemiCln
相关产品推荐
相关产品推荐

