4

我的程序出现死锁问题。所以我一直在阅读有关锁的信息,但问题是大多数信息不一致或没有平台定义。在递归锁(互斥锁)与非递归锁(互斥锁)中,最被接受的答案是:

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

在评论中人们说这是不正确的,也没有关于它的参考。所以...

1)如果我在线程A中锁定了一个非递归互斥锁。线程B可以在不抓住锁的情况下解锁它吗?

2)如果线程A和线程B调用非递归互斥锁获取锁,线程B会等到锁被释放再获得锁,还是会抛出异常?递归互斥体中的这种情况怎么样?(也在其他无法得出正确结论的问题中讨论)

3)当使用递归锁时,在进程终止时,我所有的递归锁都必须被释放吗?(取决于不会发生的过程结束的地方)

4) 谨慎使用递归锁和非递归锁的组合时,我在看什么问题?

PS:仅使用 windows 平台和std::thread.

4

4 回答 4

8

我认为阅读有关Reentrant Mutexeswiki 会对您有很大帮助。我同意其他线程中的评论;公认的答案是错误的,或者至少非常非常糟糕地解释了它的观点。

所有互斥锁都有所有权的概念。这就是使它们与Semaphore不同的原因。锁定互斥锁的线程始终是必须解锁它的线程,这就是互斥锁可能导致死锁的部分原因,也是它们为预期目的工作的原因(相互排除对特定代码块的访问)。

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

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

这是两种互斥体类型之间的全部差异。本质上,如果您在递归方法中放置互斥锁并且该方法在互斥锁被释放之前递归,则需要递归互斥锁。否则在第一次递归之后,由于无法第二次获取锁,会立即出现死锁。

确实,这是使用递归互斥锁的唯一原因;在大多数其他情况下,您将获得相同的线程尝试获取相同的锁而不释放它,这可能可以重构为正确获取/释放锁而无需递归互斥锁。这样做会更安全;假设 RAII,递归函数自然会冒泡并释放递归互斥锁上的每个锁,而在其他情况下,您可能无法充分释放互斥锁并最终导致死锁。

因此,要回答您的具体问题:

  1. 不,除非您的互斥体系统特别允许这样做
  2. 通常在这两种情况下都是可以的,尽管这又是特定于阻塞/抛出的互斥锁实现。几乎我用过的每个系统都只是块(这就是为什么如果同一个线程在没有空闲的情况下锁定两次,非递归互斥锁死锁的原因)
  3. 是的,通常情况下,尽管通常假设适当的 RAII 和进程正常终止,它们将被释放。在持有锁的同时非正常终止的进程可能有点麻烦。
  4. 看我上面的解释。具体来说,请注意 wiki 中的以下内容:

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

于 2014-10-30T14:44:28.933 回答
1

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

所以最好先检查标准库文档,例如unlock明确声明的 c++ 标准:

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

于 2014-10-30T15:02:28.803 回答
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 的同一线程锁定。如果不满足这些条件,则返回错误代码并且互斥体保持不变。 “快速”互斥锁不执行此类检查,因此允许锁定的互斥锁由其所有者以外的线程解锁。这是不可移植的行为,不得依赖。

似乎“锁定的互斥锁可能由非递归互斥锁类型的所有者以外的线程解锁”

于 2016-11-30T17:50:41.757 回答
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. 我想您正在查看一个没有竞争条件的多线程应用程序。:-)

于 2014-10-30T14:46:55.967 回答