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

Go中sync.Map的LoadOrStore是否存在竞态,会重复执行创建函数?

sync.Map的LoadOrStore后执行createFunc是否会因竞态条件多次调用?

结论

你的代码确实存在竞态条件,针对同一个key,createDatabaseEntry可能被多个goroutine重复执行。

原因分析

sync.Map.LoadOrStore本身是原子操作,能保证同一个key只会被存储一次值,但问题出在你把createFunc的调用放在了LoadOrStore之后的分支判断里:

  1. 当多个goroutine同时调用GetOrCreate且目标key不存在时,它们都会通过LoadOrStore得到loaded=false的结果(原子操作只保证最终只有一个值被存储,但所有并发调用都会感知到之前没有值);
  2. 这些goroutine都会进入if !loaded的代码块,各自执行createFunc,导致函数被多次调用。

举个你的代码的执行流程:

  • Goroutine 1和Goroutine 2同时执行hm.m.LoadOrStore("hash_1", "id_1"),此时key不存在;
  • 两个goroutine都返回(actual: "id_1", loaded: false);
  • 两者都调用createDatabaseEntry("hash_1", "id_1"),最终输出两次创建日志。

修正方案

要避免createFunc被重复执行,需要把"创建值"和"存储值"的逻辑做原子性绑定,或者用同步机制确保创建逻辑只执行一次。

方案1:容忍createFunc重复执行但保证存储值唯一

如果createFunc是幂等的(重复执行不影响结果),或者执行成本很低,可以用这种方式:

func (hm *HashIDMap) GetOrCreate(key string, createFunc func() string) string {
    // 先尝试读取已有值
    if actual, ok := hm.m.Load(key); ok {
        return actual.(string)
    }
    // 创建新值
    newValue := createFunc()
    // 尝试存储,若此时已有其他goroutine存储了值,则返回已有值
    actual, loaded := hm.m.LoadOrStore(key, newValue)
    if loaded {
        return actual.(string)
    }
    return newValue
}

这种方式下,createFunc仍可能被多次执行,但最终存储到sync.Map里的只会是一个值,调用方拿到的结果是一致的。

方案2:确保createFunc只执行一次

如果createFunc执行成本很高(比如写入数据库),必须确保只执行一次,可以结合sync.Once和sync.Map:

type HashIDMap struct {
    m        sync.Map
    oncePool sync.Map // 存储每个key对应的sync.Once
}

func (hm *HashIDMap) GetOrCreate(key string, createFunc func() string) string {
    // 先尝试读取已有值
    if actual, ok := hm.m.Load(key); ok {
        return actual.(string)
    }
    // 获取当前key对应的sync.Once,不存在则创建
    onceVal, _ := hm.oncePool.LoadOrStore(key, &sync.Once{})
    once := onceVal.(*sync.Once)

    var result string
    once.Do(func() {
        // 仅执行一次创建逻辑
        result = createFunc()
        // 将创建好的值存入主map
        hm.m.Store(key, result)
    })

    // 再次读取确保拿到正确的值
    actual, _ := hm.m.Load(key)
    return actual.(string)
}

sync.Once.Do会保证传入的函数只被执行一次,无论多少goroutine并发调用,彻底避免createFunc重复执行。

总结

sync.Map.LoadOrStore只保证值的存储是原子性的,但不会同步后续的业务逻辑。你的原始代码中,多个goroutine在判断!loaded后会无同步地执行createFunc,这就是竞态条件的根源。通过调整创建逻辑的时机或引入sync.Once,可以解决这个问题。

内容的提问来源于stack exchange,提问作者Zanko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 18:53:09