英文:
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()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论