Go 戈朗延迟有时会失败
我正在处理一个具有多个例程的应用程序。处理器接收一个ID(字符串)并执行一些操作。ID可能是重复的,我不希望在另一个例程处理ID时有多个例程来处理它 我使用了一个同步互斥映射Go 戈朗延迟有时会失败,go,mutex,Go,Mutex,我正在处理一个具有多个例程的应用程序。处理器接收一个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
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都会被删除
有时(并非总是)该值不会从映射中逐出。从日志/指标中,我确定这不是错误案例,但流程功能已完成,并且密钥未从映射中逐出
这里是否缺少互斥或延迟的任何行为
有时(并非总是)该值不会从映射中逐出
你什么时候怎么检查
您粘贴的函数的代码在完成时实际上正在删除该键,但在该执行完成后,可能会有另一个goroutine处理(从而再次添加)相同的键。我可以看到此代码导致度量函数(在
过程中检查ok
后执行)要查看地图中的值,请执行以下操作:
thread1将值添加到映射并开始处理它。它产生CPU
thread2进入进程
函数,调用addIfNotPresent
并获取false
。它在进入if!之前产生CPU!好{
thread1获取CPU,完成其工作,从映射中删除值并退出
thread3获取值,检查映射,发现值不再存在,开始处理它。它产生CPU
thread2获取CPU,检查ok
并看到它是false
。此时它会命中度量函数。度量函数检查映射并看到其中的值。随后会出现混淆
验证这一点的一种方法是在处理完成后检查映射。如果我的假设是正确的,您将不会看到任何值仍然留在那里。如果我错了,并且这些值确实没有被正确逐出,您仍然会在那里看到它们。至少您应该发布addIfNotPresent()
和delete()
实现。代码看起来不错。你确定你发布了实际的代码吗?缺少arg名称,这让我觉得我们在这里看到的可能缺少更多。我没有复制粘贴。但我确信这正是我所拥有的。证据表明并非如此。defer()没有失败。我编辑了一点。doOne
函数接受一个上下文。我通过超时传递一个上下文,并推迟取消。这有什么区别吗?这是一个POC系统,很容易通过日志/度量来跟踪ID。在过程
函数中检查ok
后,我有一个度量/日志。该度量已被获取现在以固定的间隔(间隔与传递到函数中的id相同)删除一次命中大约一天。通过其他指标,我可以看到唯一的可能性是没有删除密钥。