如何在C#中使用多个变量作为锁范围

10

我是一名有帮助的助手,能够进行文本翻译。

我有一个情况,当两个锁对象都是空闲状态时,应该执行一段代码块。

我曾经希望会有这样的做法:

lock(a,b)
{
    // this scope is in critical region
} 

然而,似乎没有这样的东西。那么这是否意味着唯一的方法是:
lock(a)
{
    lock(b)
    {
        // this scope is in critical region
    }
}

这个代码能否按照预期发挥作用?虽然它可以编译,但我不确定它是否能够实现我的预期。


这难道不会导致锁问题吗?比如一个对象锁定a,另一个对象锁定b,现在你会遇到死锁的情况,其中object1阻塞了a等待b,而object2阻塞了b等待a。虽然我对锁不是很熟悉,但这似乎很糟糕。 - Tester101
2
这就是为什么锁定顺序如同到目前为止所有答案提到的那样重要。有时你需要多把锁,这时候一个标准的模式是通过(至少是约定俗成的)对锁进行排序来强制执行。例如,每次你想获得 b 锁,你必须首先获得 a 锁。这会导致更多争用,但可以防止死锁。尽可能避免这种情况总是更好的,但有时是必要的。 - Herms
@Tester101:是的,我知道如果没有正确处理,它会导致死锁。 - Shamim Hafiz - MSFT
3个回答

18
lock(a) lock(b) { // this scope is in critical region }

这段代码会阻塞,直到线程能够获取锁a。然后,在获取了锁a之后,它会阻塞,直到线程能够获取锁b。所以这段代码按照预期工作。

然而,你必须小心不要在其他地方这样做:

lock(b) lock(a) { // this scope is in critical region }

这可能会导致死锁的情况,其中线程1已获取了a的锁并正在等待获取b的锁,而线程2已获取了b的锁并正在等待获取a的锁。


11

请求同时锁定两个应该是没问题的。lock(a)会一直阻塞,直到a空闲。一旦您获得了该锁,lock(b)将阻塞直到您获得了b。此时,您已经同时拥有了两者。

在这里,你需要非常小心的是顺序。如果你要这么做,请确保你在获取b的锁之前始终先获取a的锁。否则,您很容易会陷入死锁状态。


5

尽管有可能会导致死锁情况,但我认为它应该可以。

通常情况下,代码将尝试锁定a,然后如果成功了就接着锁定b。这意味着只有在能够锁定ab时才会执行代码。这正是你想要的。

但是,如果其他代码已经锁定了b,那么这段代码将无法按照你的期望执行。你还需要确保在需要同时锁定ab的所有地方,都按照相同的顺序获取锁。如果你先获取b,然后再获取a,就会导致死锁。


实际上,如果其他东西锁定了b,它不会失败 - 它将等待直到该锁被释放。 - TomTom
1
@TomTom:如果另一个线程只尝试访问其中一个锁,那么我们就不会有任何问题,但是如果它尝试以相反的顺序锁定a和b,那么很可能会发生死锁。@ChrisF:你的编辑中外部花括号是否必要,还是你只是为了让代码看起来更清晰? - Shamim Hafiz - MSFT
@Gunner - 只是尝试使代码看起来更清晰,并澄清锁的范围。希望可以这样做。 - ChrisF
@ChrisF:啊,没关系。我想这些锁定作用域就像其他作用域一样工作。例如,在许多嵌套的for循环中,我们通常不为外部循环放置大括号。 - Shamim Hafiz - MSFT
@Gunner - 是的,最终只是编码风格的问题,但我觉得当你在没有IDE工具帮助的情况下查看代码时,它会增加一些清晰度。 - ChrisF
只是补充一下我的看法(虽然已经晚了一年),大家一直在避而不谈的排序问题被称为餐厅哲学家问题:http://en.wikipedia.org/wiki/Dining_philosophers_problem - John McDonald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接