递归和非递归锁(互斥锁)

时间:2014-10-30 14:30:26

标签: c++ multithreading mutex recursive-mutex

我的程序遇到了死锁问题。所以我一直在阅读关于锁的问题,但问题是大多数信息是不一致的或不是平台定义的。 Recursive Lock (Mutex) vs Non-Recursive Lock (Mutex)最受欢迎的答案是:

  

因为递归互斥锁具有所有权感,所以线程就是这样   抓取互斥锁必须与释放互斥锁的线程相同。在里面   非递归互斥体的情况,没有所有权和任何意义   无论最初哪个线程,线程通常都可以释放互斥锁   拿了互斥量。在许多情况下,这种类型的“互斥体”实际上更多   信号量操作,您不一定使用互斥锁   排除设备,但将其用作同步或信号设备   在两个或多个线程之间。

在评论中,人们说这是不正确的,没有任何参考。所以......

1)如果我在线程A中锁定非递归互斥锁。线程B可以解锁它而不需要锁定吗?

2)如果在线程A和线程B调用获取锁定的非递归互斥锁中的锁定中,线程B将等待直到锁定被释放以获得锁定还是会抛出异常?这个案例在递归互斥体中怎么样? (在其他问题中也讨论过,不能得出正确的结论)

3)当使用递归锁时,在进程终止时,是否必须释放所有递归锁? (取决于流程结束时不会发生的情况)

4)在谨慎使用递归锁和非递归锁的组合时,我看到了哪些问题?

PS:仅使用Windows平台和std::thread

4 个答案:

答案 0 :(得分:7)

我认为通过阅读Reentrant Mutexes上的wiki,您将获得非常的帮助。我同意其他主题中的评论;接受的答案是错误的,或至少解释它的观点非常非常糟糕。

所有Mutexes都拥有所有权概念。这就是它们与Semaphore不同的原因。锁定互斥锁的线程始终是必须解锁它的线程,这也是互斥锁导致死锁的原因之一,也是为什么它们可以用于预期目的(相互排除对特定的访问权限)代码块。)

那么Recursive / Reenrant和常规Mutex有什么区别?递归互斥锁可以被同一个线程多次锁定。引用维基:

  

递归锁(也称为递归线程互斥锁)是允许线程以递归方式获取它所持有的相同锁的那些锁。请注意,此行为与普通锁定不同。在正常情况下,如果已经持有普通锁的线程尝试再次获取相同的锁,则它将死锁。

这是两种互斥体类型之间的全部区别。实际上,如果您在递归方法中放置互斥锁并且该方法在释放互斥锁之前进行递归,则需要递归互斥锁。否则在第一次递归之后,会出现即时死锁,因为无法再次获取锁定。

实际上,这是使用递归互斥锁的唯一原因;大多数其他情况下你会得到相同的线程试图获得相同的锁而不释放它可能会被重构为正确获取/释放锁而不需要递归互斥锁。这样做会更安全;递归函数自然会冒出来并释放递归互斥锁上的每一个锁,假设是RAII,在其他情况下,你可能没有充分释放互斥锁并且仍然会出现死锁。

所以,回答你的具体问题:

  1. 不,除非您的互斥系统明确允许此
  2. 在这两种情况下都是肯定的,但这又是关于阻止/抛出的互斥实现。几乎我使用的每个系统都只是阻塞(这就是为什么非递归互斥锁死锁,如果相同的线程锁定两次没有空闲)
  3. 是的,通常情况下,尽管通常它们会在假设正确的RAII时被释放,并且该过程会优雅地终止。在持有锁的情况下非优雅地终止进程可能会有点麻烦。
  4. 请参阅上面的解释。具体来说,请注意wiki中的以下内容:
  5.   

    请注意,当且仅当获取的递归锁定次数与所有者线程释放的次数相匹配时,才会释放递归锁定。

答案 1 :(得分:1)

您指的是POSIX互斥体讨论,但Windows无论如何都不支持POSIX,您使用的c ++标准线程原语可能在细节上有所不同。

首先检查标准库文档,例如unlock的c ++标准明确指出:

  

要求:调用线程应拥有互斥锁。

答案 2 :(得分:1)

以下内容来自Linux pthread_mutex_lock手册页,

pthread_mutex_t类型的变量也可以使用常量 PTHREAD_MUTEX_INITIALIZER(对于快速互斥锁),THREAD_RECURSIVE_MUTEX_INITIALIZER_NP(对于递归互斥锁)和PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP(用于错误检查互斥锁)静态初始化。

error checking'' and递归''互斥锁上,pthread_mutex_unlock实际上在运行时检查互斥锁是否在入口处被锁定,并且它被现在调用pthread_mutex_unlock的同一线程锁定。如果不满足这些条件,则返回错误代码并且互斥锁保持不变。 ``Fast''互斥锁不执行此类检查,因此允许锁定的互斥锁由其所有者以外的线程解锁。这是非可移植行为,不得依赖它。

似乎“锁定的互斥锁可能被其所有者以外的其他线程解锁,并且具有非递归的互斥锁类型”

答案 3 :(得分:0)

真的,你应该编写一个简单的程序来测试这些案例。

  1. 假设我们正在使用互斥锁,另一个线程永远不应该解锁互斥锁。任何线程都可以解锁互斥锁。 (编辑:我从未尝试用另一个线程解锁互斥锁。这会破坏目的)这会导致竞争条件。

  2. 考虑代码:

    void criticalSection(){
        pthread_mutex_lock(&mutex)
        //dostuff
        pthread_mutex_unlock(&mutex)
    }
    

    如果有两个线程,线程A和B,以及线程A首先进入该函数,它将获取锁并执行操作。如果线程B进入,而线程A仍在函数中,则它将被锁定。线程A执行时

      pthread_mutex_unlock(&mutex)
    

    线程B现在将被“唤醒”并开始做事。

  3. 我想你可以做任何你想做的事,但是如果有递归锁意味着一个线程仍在做某事你可能想等待它完成。

  4. 我想你正在看一个没有竞争条件的多线程应用程序。 : - )