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

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

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

defer mtx.Unlock()
mtx.Lock()

这在我看来是错误的,我更喜欢在执行

Unlock
后推迟
Lock
的惯用方式,但是
Mutex.Lock
的文档没有指定
Lock
会失败的情况。因此,早期
defer
模式的行为应该与惯用方式相同。

我的问题是:是否有令人信服的理由表明这种模式较差? (例如,

Lock
可能会失败,然后延迟的
Unlock
panic
),因此应该更改代码还是应该保持原样?

go mutex deferred
1个回答
5
投票

简短回答:

是的,没关系。

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

更长、更细致的答案:

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

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)。
  • Defers 可以改变你最终调用的函数的返回值(如果你使用命名返回)
© www.soinside.com 2019 - 2024. All rights reserved.