我正在做一个有多个例程的应用程序。处理器接收一个ID(字符串)并执行一些操作。ID可能是重复的,我不希望多个例程在处理一个ID时,另一个例程正在处理它。
我使用了一个同步互斥映射来处理它。
type cache struct{
sync.Mutex
ids map[string]struct{}
}
func(c *cache) addIfNotPresent(string)bool{
c.Lock()
defer c.Unlock()
if _, ok := c.ids[id]; ok{
return false
}
c.ids[id] = struct{}{}
return true
}
func(c *cache) delete(string){
c.Lock()
defer c.Unlock()
delete(c.ids, id)
}
我的处理器有一个这个映射的实例。现在我的进程看起来像这样
func process(string){
ok := cache.addIfNotPresent(id)
if !ok{
return
}
defer cache.delete(id)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := doOne(ctx)
if err {
return err
}
...
return nil
}
使用defer,所以无论处理器中发生什么,id都会被移除。
有时(并不总是)该值不会从地图中被驱逐。从日志指标来看,我确定这不是错误的情况,但是处理函数已经完成,并且键没有从地图中被驱逐。
我是否遗漏了mutex或defer的任何行为?
有时(并不总是)该值不会从地图中被驱逐。
你是如何和何时检查的?
你粘贴的函数代码实际上是在完成后删除键,但在执行结束后,可能会有另一个goroutine再次处理(从而添加)相同的键。
我可以看到这个代码的唯一方法是导致你的度量函数(在检查了 ok
在 process
)查看地图中的值的方法如下。
process
函数,调用 addIfNotPresent
并得到 false
. 它产生的CPU在进入 if !ok {
.ok
并看到它的 false
. 这时,它打到了你的度量函数。你的度量函数检查地图,看到那里的值。随之而来的是混乱。一个验证的方法是在处理完成后检查地图。如果我的假设是正确的,你将不会看到任何值仍然滞留在那里。如果我的假设是错的,数值真的没有被正确地驱逐,你仍然会看到它们在那里。