可以在锁定之前推迟解锁吗

时间:2018-08-07 08:35:56

标签: go mutex deferred

我正在查看一些现有代码,并看到重复了几次

defer mtx.Unlock()
mtx.Lock()

这对我来说似乎是错误的,我更喜欢在执行Unlock之后推迟使用Lock的惯用方式,但是Mutex.Lock的文档没有指定{{ 1}}将失败。因此,早期Lock 模式的行为应与惯用方式相同。

我的问题是:是否有令人信服的理由说这种模式不如? (例如defer可能失败,然后延迟的LockUnlock),因此应该更改代码还是应该保留原样?

1 个答案:

答案 0 :(得分:3)

简短回答:

是的,可以。在函数返回(好了,有点)之后进行defer调用。

更长,更细腻的答案:

这是有风险的,应该避免。在您的2行代码段中,这不会有问题,但是请考虑以下代码:

func (o *Obj) foo() error {
    defer o.mu.Unlock()
    if err := o.CheckSomething(); err != nil {
        return err
    }
    o.mu.Lock()
    // do stuff
}

在这种情况下,互斥锁可能根本没有被锁定,或者更糟:它可能被锁定在另一个例程上,最终您将其解锁。同时,又有一个例程获得了它本不应该拥有的锁,您将获得数据争用,或者在该例程返回时,解锁调用将出现恐慌。 调试此类混乱情况是您梦a以求的,应该不惜一切代价避免。

除了看起来直观且容易出错的代码外,defer确实要付出一定的代价,具体取决于您要实现的目标。在大多数情况下,成本是很小的,但是如果您要处理绝对是时间紧迫的事情,通常最好在需要时手动添加解锁调用。一个不使用defer的好例子是,如果您要在map[string]interface{}中缓存内容:我将创建一个结构,其中包含缓存的值和一个sync.RWMutext字段以供同时使用。如果我经常使用此缓存,则延迟调用可能会开始累加。它可能看起来有些混乱,但要视具体情况而定。性能就是您想要的目标,或者是更简短的可读性更高的代码。

关于延迟的其他注意事项:

  • 如果函数中有多个延迟,则将定义它们的调用顺序(LIFO)。
  • 延迟可以更改您最终调用的函数的返回值(如果使用命名返回)