Golang defer有时会失败

问题描述 投票:0回答:1

我正在做一个有多个例程的应用程序。处理器接收一个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的任何行为?

go mutex
1个回答
1
投票

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

你是如何和何时检查的?

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


0
投票

我可以看到这个代码的唯一方法是导致你的度量函数(在检查了 okprocess)查看地图中的值的方法如下。

  1. 线程1将值添加到地图中并开始处理它。它产生的CPU。
  2. 线程2进入 process 函数,调用 addIfNotPresent 并得到 false. 它产生的CPU在进入 if !ok {.
  3. 线程1得到CPU,完成工作,从地图上删除数值,退出。
  4. 线程3得到值,检查地图,看到值不在了,开始处理。它产生了CPU。
  5. 线程2得到CPU,检查地图,发现值不在了,开始处理。ok 并看到它的 false. 这时,它打到了你的度量函数。你的度量函数检查地图,看到那里的值。随之而来的是混乱。

一个验证的方法是在处理完成后检查地图。如果我的假设是正确的,你将不会看到任何值仍然滞留在那里。如果我的假设是错的,数值真的没有被正确地驱逐,你仍然会看到它们在那里。

© www.soinside.com 2019 - 2024. All rights reserved.