You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:39:11