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

判断Go语言handleCity函数是否线程安全及原因分析

问题:handleCity函数是否线程安全?

代码实现

通用缓存CacheMap

type CacheMap[K comparable, V any] struct {
  sync.Map
}

func (cache *CacheMap[K, V]) Set(key K, value V) {
  cache.Store(key, value)
}

func (cache *CacheMap[K, V]) Get(key K) (V, bool) {
  var value V

  data, ok := cache.Load(key)
  if !ok {
    return value, false
  }

  return data.(V), true
}

全局缓存与锁

var CitiesCache CacheMap[string, int64]
var CityLock sync.Mutex

handleCity函数

func handleCity(cityName string) (pgtype.Int8, error) {
  if cityName == "" {
    return pgtype.Int8{}, nil
  }

  cityId, exists := CitiesCache.Get(cityName)
  if exists {
    return pgtype.Int8{Int64: cityId, Valid: true}, nil
  }

  filters := url.Values{
    "name":    []string{cityName},
    "__limit": []string{"1"},
  }

  CityLock.Lock()
  defer CityLock.Unlock()

  dbResponse, err := dbutils.ReadCities(context.Background(), DB_POOL, filters)
  if err != nil {
    return pgtype.Int8{}, fmt.Errorf(`error while reading cities: %v`, err)
  }

  if len(dbResponse.Cities) == 0 {
    newCity := models.City{
      Name: pgtype.Text{String: cityName, Valid: true},
    }

    cityId, err = dbutils.InsertCity(context.Background(), DB_POOL, &newCity)
    if err != nil {
      return pgtype.Int8{}, fmt.Errorf(`error while inserting city: %v`, err)
    }
  } else {
    cityId = dbResponse.Cities[0].Id.Int64
  }

  CitiesCache.Set(cityName, cityId)
  return pgtype.Int8{Int64: cityId, Valid: true}, nil
}

分析结论

这个handleCity函数是线程安全的,但存在可优化的冗余操作。

线程安全的依据

  1. 缓存操作安全:CitiesCache基于sync.Map实现,其Get(底层调用sync.Map.Load)和Set(底层调用sync.Map.Store)本身就是线程安全的,不会出现数据竞争问题。
  2. 数据库操作互斥:CityLock作为全局互斥锁,确保同一时间只有一个goroutine能执行锁内的数据库查询与插入逻辑:
    • 多个goroutine发现缓存缺失后,会排队等待获取锁;
    • 第一个拿到锁的goroutine完成数据库操作(查询并按需插入)后,将城市ID存入缓存;
    • 后续goroutine拿到锁后,查询数据库会发现城市已存在,直接获取现有ID并更新缓存(重复更新不破坏数据一致性)。

可优化点(性能层面)

当前实现缺少双重检查锁定的关键步骤:在获取CityLock后没有再次检查缓存。这会导致多个goroutine在缓存缺失场景下,即使第一个goroutine已经将ID存入缓存,后续goroutine仍会执行冗余的数据库查询。

优化后的锁内逻辑应添加缓存二次检查:

CityLock.Lock()
defer CityLock.Unlock()

// 双重检查:确认缓存是否已被其他goroutine更新
if cityId, exists := CitiesCache.Get(cityName); exists {
    return pgtype.Int8{Int64: cityId, Valid: true}, nil
}

// 原数据库查询与插入逻辑...

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:14:54