Best way to implement an atomic counter in map in Go

huangapple go评论68阅读模式
英文:

Best way to implement an atomic counter in map in Go

问题

我想在Go语言中实现一个原子计数器,它存储在一个map(map[string]int64)中,该如何实现呢?首先,我不能直接使用int64作为键,因为在map中无法获取该值的地址以供atomic.AddInt64函数使用,因为map是不可寻址的。其次,当值使用*int64时,我必须以某种方式预先初始化指针的int值,但是在if _, ok := myMap[key]; !ok { myMap[key] = ... }中进行的初始化不会起作用,因为它不是原子操作,因此需要使用锁。

英文:

I would like to implement an atomic counter in a map (map[string]int64), how can it be implemented in Go? First, I cannot just use int64 as a key, because I can't take address of this value in map for atomic.AddInt64 func, because maps are not addressable. Second, when using *int64 as value, I must somehow preinitialize int value for the pointer, but initialization on if _, ok := myMap[key]; !ok { myMap[key] = ... } won't work, because it will not be atomic, and therefore will require a lock.

答案1

得分: 2

sync.Map有一个适用于你的用例的方法,即(*sync.Map).LoadOrStore,它会在可能的情况下加载现有值,并在否则将给定值存储到映射中。

func UpdateCounter(counters *sync.Map, key string) {
    val, _ := counters.LoadOrStore(key, new(int64))
    ptr := val.(*int64)
    atomic.AddInt64(ptr, 1)
}

这个过程分为两步:

  • 第一步是规范化条目,即无论执行顺序如何,都获得相同的键对应的条目。(*sync.Map).LoadOrStore通过原子方式查找映射,如果不存在则放入条目,否则从映射中获取条目来确保这一点。

  • 第二步是在获取规范条目后递增计数器。由于同一键的所有查找都返回相同的条目,所以这一步是简单的。

英文:

sync.Map has the exact method for your use case, that is (*sync.Map).LoadOrStore, which will load an existing value if is able to, and store the given value into the map otherwise.

func UpdateCounter(counters *sync.Map, key string) {
    val, _ := counters.LoadOrStore(key, new(int64))
    ptr := val.(*int64)
    atomic.AddInt64(ptr, 1)
}

This is done in 2 steps:

  • The first one is to canonicalise the entry. i.e. obtain the same entry for the same key regardless of execution order. (*sync.Map).LoadOrStore ensures that by atomically looking up the map, putting the entry if absent, or getting the entry from the map otherwise.

  • The second step is to increment the counter after obtaining the canonical entry. Since all lookups on the same key return the same entry, this step is trivial.

答案2

得分: 0

Golang在sync包中有一个同步的映射(sync.Map),文档可以在这里找到。文档中提到:

> 当多个goroutine对不相交的键集进行读取、写入和覆盖时。

这似乎符合你的使用情况,并且文档中表示它比带有互斥锁的映射更好。

英文:

Golang has a synced map in the sync package as documented here, it says that:

> when multiple goroutines read, write, and overwrite entries for disjoint sets of keys.

Which seems to be your use-case, and it says it is better than a map with a mutex.

答案3

得分: -1

我认为在这里使用LoadOrStore方法并不能起到帮助作用,因为你正在尝试对读取和写入(增加)操作进行原子事务处理。
最好的做法是为你要更新的每个条目使用一个锁的映射:

urlCache = expiringmap.New[string, int](config.ConfigFile.Ttl)
enrtyLock, _ := r.urlCacheLocks.Get(url)
enrtyLock.Lock()
// 在这里进行你的读取和写入事务操作 //
enrtyLock.Unlock()
英文:

I think LoadOrStore won't be helping here as you're trying to make an atomic transaction for the read and write (increment) operations.
It will be better to use a map of locks for each entry you're trying to update:

urlCache = expiringmap.New[string, int](config.ConfigFile.Ttl)
enrtyLock, _ := r.urlCacheLocks.Get(url)
enrtyLock.Lock()
//your get and set transaction here//
enrtyLock.Unlock()

huangapple
  • 本文由 发表于 2022年11月17日 15:38:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/74471748.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定