解锁互斥锁的顺序在这里有所不同吗?

时间:2012-02-23 14:20:16

标签: c++ multithreading pthreads deadlock

假设我有两个变量protected_var1protected_var2。让我们进一步假设这些变量是通过多个线程更新的,并且相当独立,通常是一个或另一个但不是两个都被处理 - 因此它们都有自己的互斥保护以提高效率。

假设:

- 我总是在需要两个锁定的区域中按顺序锁定互斥锁(mutex1然后是mutex2)。

- 两个互斥体被其他许多地方使用(比如只是锁定mutex1,或者只是锁定mutex2)。

在这种情况下,使用两者在函数末尾解锁互斥锁的顺序是否有所不同?

void foo()
{
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);

    int x = protected_var1 + protected_var2;

    pthread_mutex_unlock(&mutex1); //Does the order of the next two lines matter?
    pthread_mutex_unlock(&mutex2);
}

很久以前,我在一次关于这种情况的采访中被问到一个问题,我觉得答案是肯定的 - 这两个解锁的顺序很重要。我不能为我的生活弄清楚如何在这两者都被使用的情况下以相同的顺序获得锁定时如何导致死锁。

9 个答案:

答案 0 :(得分:11)

只要您不尝试获取,订单就无所谓了 发行版之间的另一个锁。重要的是永远 以相同的顺序获得锁;否则,你冒着陷入僵局的风险。

编辑:

要扩展约束:您必须在其中建立严格的排序 互斥体,例如, mutex1位于mutex2之前(但此规则适用于 任意数量的互斥体)。如果您,您只能请求锁定互斥锁 不要在订单后面拿一个互斥量;例如你不可以 如果您在mutex1上锁定,则请mutex2锁定。随时 这些规则得到尊重,你应该是安全的。关于 释放,如果你释放mutex1,然后尝试重新获取它 发布mutex2,您违反了规则。在这方面,有可能 在遵循类似堆栈的顺序方面有一些优势:最后获得的是 总是第一次发布。但它有点间接影响: 规则是,如果您持有mutex1,则无法请求锁定mutex2 mutex1。无论你在mutex2时是否锁定了 获得了{{1}}上的锁定。

答案 1 :(得分:7)

  

我不能为我的生活弄清楚如何在这两者被使用的情况下以相同的顺序获得锁定时如何导致死锁。

在这种情况下,我认为解锁互斥锁的顺序可能不会导致死锁。

由于pthread_mutex_unlock()没有阻止,无论两次调用的顺序如何,两个互斥锁总是会被解锁。

请注意,如果您尝试在两个解锁呼叫之间获取任何锁,则可以完全更改图片。

答案 2 :(得分:3)

锁定的正确性无关紧要。原因是,即使假设某些其他线程正在等待锁定mutex1然后再锁定mutex2,最糟糕的情况是,一旦释放mutex1(并获取mutex1),它就立即被安排。然后它阻止等待mutex2,你问的线程会在它再次被调度后立即释放,并且没有理由不应该很快发生(如果这些是唯一的两个线程,则立即发生)。 / p>

因此,如果您首先发布了mutex2,那么性能在这种情况下的成本可能会很小,因此只有一次重新安排操作。然而,你通常没有预料到或担心的是,所有这些都在“安排通常不具有确定性”的范围内。

然而,释放锁定的顺序肯定会影响调度。假设有两个线程在等待你的线程,其中一个线程在mutex1上被阻塞而另一个在mutex2上被阻塞。可能会发现,无论你首先释放哪个锁,该线程都会首先运行,只是因为你的线程已经过了它的欢迎(消耗的时间超过整个时间片),因此只要其他任何东西可以运行就会被取消安排。但是这不会导致其他正确的程序出错:一旦释放第一个锁,就不允许依赖对你的线程进行计划。因此,无论是两个等待线程运行的顺序,如果你有多个核心,它们都是同时运行,或者两个交替运行在一个核心上,无论你发布锁定的顺序都必须同样安全。

答案 3 :(得分:1)

解锁顺序不会导致死锁。但是,如果有机会,我建议以反向锁定顺序解锁它们。这对代码的运行影响可以忽略不计。但是,开发人员习惯于考虑范围,并且范围以相反的顺序“关闭”。以相反的顺序看到它们以简单地考虑范围锁定。这让我想到了第二点,在大多数情况下,最安全的是用一个基于堆栈的防护来替换直接调用来锁定和解锁。这样做可以为最少的心理努力提供最大程度的正确性,并且在存在异常时也是安全的(这可以通过手动解锁来实现)!

一个简单的守卫(那里有很多......这只是一个快速自己动手):

class StMutexLock
{
    public:
        StMutexLock(pthread_mutex_t* inMutex)
        : mMutex(inMutex)
        {
            pthread_mutex_lock(mMutex);
        }

        ~StMutexUnlock()
        {
            pthread_mutex_unlock(mMutex);
        }
    private:
        pthread_mutex_t*   mMutex;
}

{
    StMutexLock lock2(&mutex2);
    StMutexLock lock1(&mutex1);

    int x = protected_var1 + protected_var2;
    doProtectedVar1ThingThatCouldThrow(); // exceptions are no problem!
    // no explicit unlock required.  Destructors take care of everything
}

答案 4 :(得分:0)

不,没关系。这种情况不会造成僵局;两个解锁操作符都可以保证成功(条形堆损坏或类似问题)。

答案 5 :(得分:0)

解锁的顺序在这里不是问题,但锁定的顺序可能是个问题。

考虑:

void foo()
{
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);

    int x = protected_var1 + protected_var2;

    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
}

void bar()
{
    pthread_mutex_lock(&mutex2);
    pthread_mutex_lock(&mutex1);

    int x = protected_var1 + protected_var2;

    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
}

这可能导致死锁,因为foo可能已锁定mutex1,现在在mutex2已锁定bar时等待mutex2,现在等待{ {1}}。因此,以某种方式确保嵌套的互斥锁始终以相同的顺序锁定是个好主意。

答案 6 :(得分:0)

我能看到的是,如果另一项操作正在进行mutex2并将其保留很长时间,那么foo()功能将在pthread_mutex_lock(&mutex1);之后停滞,可能会受到一些影响

答案 7 :(得分:0)

只要var1和var2被锁定,无论释放顺序如何,它们都会以相同的顺序锁定。事实上,按照它们被锁定的顺序释放是在STL和BOOST锁中发现的RAII释放行为。

答案 8 :(得分:0)

  override func preferredStatusBarStyle() -> UIStatusBarStyle {
            return .lightContent
        }

    extension UIColor{

        convenience init (r: CGFloat, g: CGFloat, b: CGFloat) {
            self.init(red: r/255, green: g/255, blue: b/255, alpha: 1)
        }

    }

我想这可以防止死锁