在锁定之前推迟解锁是否可以
Is it OK to defer an Unlock before a Lock
我正在检查一些现有代码并看到它重复了几次
defer mtx.Unlock()
mtx.Lock()
这对我来说看起来不对,我更喜欢在执行 Lock
之后推迟 Unlock
的惯用方式,但是 Mutex.Lock
的文档没有指定Lock
会失败。因此 early defer
模式的行为应该与惯用方式相同。
我的问题是:是否有令人信服的案例表明这种模式较差? (例如 Lock
可能会失败,然后延迟的 Unlock
会 panic
)因此应该更改代码还是应该保持原样?
简答:
是的,没关系。 defer
调用是在函数 return 之后进行的(好吧,有点)。
更长、更细致的答案:
这是有风险的,应该避免。在您的 2 行代码段中,这不会成为问题,但请考虑以下代码:
func (o *Obj) foo() error {
defer o.mu.Unlock()
if err := o.CheckSomething(); err != nil {
return err
}
o.mu.Lock()
// do stuff
}
在这种情况下,互斥量可能根本没有被锁定,或者更糟:它可能被另一个例程锁定,而您最终将其解锁。与此同时,另一个例程获得了一个它实际上不应该拥有的锁,你要么得到数据竞争,要么到例程 returns 时,解锁调用将崩溃。
调试这种混乱是一场噩梦,应该不惜一切代价避免。
除了代码看起来直观且更容易出错(具体取决于您要实现的目标)之外,defer
确实是有代价的。在大多数情况下,成本是微不足道的,但如果您处理的是绝对时间紧迫的事情,通常最好在需要的地方手动添加解锁调用。我不使用 defer 的一个很好的例子是,如果你在 map[string]interface{}
中缓存内容:我会创建一个包含缓存值的结构和一个 sync.RWMutext
字段以供并发使用。如果我经常使用这个缓存,延迟调用可能会开始增加。它可能看起来有点混乱,但它视具体情况而定。要么性能是您的目标,要么更短、更易读的代码。
关于延迟的其他注意事项:
- 如果您在一个函数中有多个延迟,则定义它们的调用顺序 (LIFO)。
- 延迟可以改变您最终调用的函数的 return 值(如果您使用命名 returns)
我正在检查一些现有代码并看到它重复了几次
defer mtx.Unlock()
mtx.Lock()
这对我来说看起来不对,我更喜欢在执行 Lock
之后推迟 Unlock
的惯用方式,但是 Mutex.Lock
的文档没有指定Lock
会失败。因此 early defer
模式的行为应该与惯用方式相同。
我的问题是:是否有令人信服的案例表明这种模式较差? (例如 Lock
可能会失败,然后延迟的 Unlock
会 panic
)因此应该更改代码还是应该保持原样?
简答:
是的,没关系。 defer
调用是在函数 return 之后进行的(好吧,有点)。
更长、更细致的答案:
这是有风险的,应该避免。在您的 2 行代码段中,这不会成为问题,但请考虑以下代码:
func (o *Obj) foo() error {
defer o.mu.Unlock()
if err := o.CheckSomething(); err != nil {
return err
}
o.mu.Lock()
// do stuff
}
在这种情况下,互斥量可能根本没有被锁定,或者更糟:它可能被另一个例程锁定,而您最终将其解锁。与此同时,另一个例程获得了一个它实际上不应该拥有的锁,你要么得到数据竞争,要么到例程 returns 时,解锁调用将崩溃。 调试这种混乱是一场噩梦,应该不惜一切代价避免。
除了代码看起来直观且更容易出错(具体取决于您要实现的目标)之外,defer
确实是有代价的。在大多数情况下,成本是微不足道的,但如果您处理的是绝对时间紧迫的事情,通常最好在需要的地方手动添加解锁调用。我不使用 defer 的一个很好的例子是,如果你在 map[string]interface{}
中缓存内容:我会创建一个包含缓存值的结构和一个 sync.RWMutext
字段以供并发使用。如果我经常使用这个缓存,延迟调用可能会开始增加。它可能看起来有点混乱,但它视具体情况而定。要么性能是您的目标,要么更短、更易读的代码。
关于延迟的其他注意事项:
- 如果您在一个函数中有多个延迟,则定义它们的调用顺序 (LIFO)。
- 延迟可以改变您最终调用的函数的 return 值(如果您使用命名 returns)