【问题标题】:Golang defer fails at timesGolang 延迟有时会失败
【发布时间】:2020-06-11 17:35:10
【问题描述】:

我正在开发一个包含多个例程的应用程序。处理器接收一个 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。

有时(并非总是)该值不会从地图中逐出。从日志/指标来看,我确定这不是错误情况,但过程功能已完成,并且密钥未从地图中逐出。

我在这里遗漏了互斥或延迟的任何行为吗?

【问题讨论】:

  • 您至少应该发布addIfNotPresent()delete() 实现。
  • 代码看起来不错。您确定您发布了实际代码吗?缺少 arg 名称,这让我认为我们在这里看到的内容可能缺少更多。
  • 我没有复制粘贴。但我确信这正是我所拥有的。
  • 证据表明并非如此。 defer() 不会失败。
  • 我编辑了一下。 doOne 函数接受上下文。我通过超时的上下文并推迟取消。有什么区别吗?

标签: go mutex


【解决方案1】:

有时(并非总是)该值不会从地图中逐出。

您是如何以及何时检查的?

您粘贴的函数的代码实际上是在完成时删除键,但在执行完成后可能会有另一个 goroutine 处理(从而添加)相同的键。

【讨论】:

  • 这是一个 POC 系统,借助日志/指标可以轻松跟踪 id。在process 函数中检查ok 后,我有一个指标/日志。现在,该指标已经定期受到大约一天的影响(间隔与传递给函数的 id 相同)。与其他指标一起,我可以看到唯一的可能性是密钥没有被驱逐。
【解决方案2】:

我可以看到此代码导致您的指标函数(在检查 process 中的 ok 后执行)以查看地图中的值的唯一方法如下:

  1. thread1 将值添加到映射并开始处理它。它产生 CPU。
  2. thread2 进入process 函数,调用addIfNotPresent 并获得false。在进入 if !ok { 之前,它会产生 CPU。
  3. thread1 获取 CPU,完成其工作,从映射中删除值并退出。
  4. thread3 获取值,检查映射,发现值不再存在,开始处理它。它产生 CPU。
  5. thread2 获取 CPU,检查 ok 并看到它是 false。在这一点上,它达到了您的指标功能。您的指标函数检查地图并查看那里的值。随之而来的是混乱。

验证这一点的一种方法是在处理完成后检查地图。如果我的假设是正确的,您将看不到任何价值仍然存在。如果我错了并且这些值真的没有被正确驱逐,你仍然会在那里看到它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-12
    • 2014-09-03
    • 2015-03-25
    • 1970-01-01
    • 2012-07-02
    • 1970-01-01
    相关资源
    最近更新 更多