【问题标题】:Is it OK to defer an Unlock before a Lock是否可以在锁定之前推迟解锁
【发布时间】:2018-08-07 08:35:56
【问题描述】:

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

defer mtx.Unlock()
mtx.Lock()

这在我看来是错误的,我更喜欢在执行Lock 之后推迟Unlock 的惯用方式,但Mutex.Lock 的文档没有指定Lock 将失败的情况。因此早期defer 模式的行为应该与惯用方式相同。

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

【问题讨论】:

  • 看起来很奇怪,但它好的。很久以前有人声称你先推迟更快(不确定是否仍然 - 或根本 - 真的)。我认为它是劣等的,因为它违反了代码的常见线性阅读。 Lock tan defer 是“让我们拿到锁,我们有锁:不要忘记解锁它”。倒过来的版本是“别忘了解锁我接下来要锁的锁”。
  • 感谢@Volker,请考虑将其添加为答案。
  • 虽然@Volker 是正确的,但我认为这是一种反模式。原因很简单:您通常使用defer 来撤消已经完成的操作。 毕竟,这是非常简单的逻辑。因此,虽然延迟解锁尚未锁定的内容在技术上很好,但在逻辑上却不是很好,这里我们得出了这样一个原则:“必须编写程序供人们阅读,并且只是偶然地供机器执行。”

标签: go mutex deferred


【解决方案1】:

简答:

是的,没关系。 defer 调用是在函数返回后进行的(嗯,有点)。

更长、更细致的答案:

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

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

在这种情况下,互斥锁可能根本没有被锁定,或者更糟糕的是:它可能被另一个例程锁定,而你最终解锁了它。与此同时,另一个例程获得了一个它确实不应该拥有的锁,并且您要么获得数据竞争,要么在例程返回时,解锁调用将出现恐慌。 调试这种混乱是你可以做的噩梦,应该不惜一切代价避免。

除了代码看起来直观且更容易出错(取决于您要实现的目标)之外,defer 确实是有代价的。在大多数情况下,成本相当微不足道,但如果您正在处理绝对时间紧迫的事情,通常最好在需要的地方手动添加解锁调用。我不使用 defer 的一个很好的例子是,如果你在 map[string]interface{} 中缓存东西:我会创建一个包含缓存值的结构和一个 sync.RWMutext 字段以供并发使用。如果我经常使用此缓存,则延迟调用可能会开始累加。它可能看起来有点混乱,但要根据具体情况而定。要么性能是您的目标,要么是更短、更易读的代码。

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

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

【讨论】:

  • 所以Lock 不可能被panic 以某种方式打断? (我对go 还很陌生,在其他语言中,线程可能由于多种原因而出现异常)。
  • @Motti:首先,根据定义,锁的实际获取是原子操作。其次,例程并不完全是线程,恐慌也不是例外。恐慌通常会导致您的应用程序崩溃。未捕获的恐慌将意味着获得或未获得锁,如果获得了它可能不会被释放。但谁在乎呢,运行时无论如何都会终止一切。我一点也不担心
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 1970-01-01
  • 2017-01-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多