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

Recursive and Non-Recursive Locks (Mutex)

本文关键字:递归 互斥      更新时间:2023-10-16

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

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

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

1) 如果我在线程a中锁定了一个非递归互斥锁,线程B可以在不获取锁的情况下解锁它吗?

2) 如果一个锁是由线程a和线程B在非递归互斥体中调用以获取该锁的,线程B会等到该锁释放后再获取该锁吗?还是会抛出异常?递归互斥中的这种情况如何?(在其他无法得出像样结论的问题中也进行了讨论)

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

4) 当谨慎地使用递归锁和非递归锁的组合时,我会发现什么问题?

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

我认为通过阅读Reentrant Mutexes上的wiki,您将得到极大的帮助。我同意其他方面的意见;公认的答案是错误的,或者至少非常非常糟糕地解释了它的观点。

所有Mutex都有所有权的概念。这就是它们与信号量不同的地方。锁定互斥体的线程总是必须解锁它的线程,这也是互斥体会导致死锁的部分原因,也是它们为其预期目的工作的原因(为了相互排除对特定代码块的访问)。

那么递归/重入和正则互斥之间有什么区别呢?递归互斥可以被同一个线程多次锁定。引用wiki:

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

这就是这两种互斥类型之间的全部区别。本质上,如果在递归方法中放置互斥锁,并且该方法在释放互斥之前递归,则需要递归互斥。否则,在第一次递归之后,将出现即时死锁,因为无法第二次获取锁。

实际上,这是使用递归互斥的唯一原因;在大多数其他情况下,如果同一个线程试图获取同一个锁而不释放它,那么可能可以重构为正确地获取/释放锁,而不需要递归互斥。这样做会更安全;假设RAII,递归函数自然会弹出并释放递归互斥体上的每个锁,在其他情况下,您可能无法充分释放互斥体,但最终仍会出现死锁。

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

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

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

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

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

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

以下来自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"互斥体不执行这样的检查,因此允许被锁定的互斥体由其所有者以外的线程解锁这是不可移植的行为,不可依赖。

看起来"一个锁定的互斥体可能会被非递归互斥体类型的所有者以外的线程解锁"

实际上,你应该写一个简单的程序来测试这些情况。

  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. 我想您看到的是一个没有竞争条件的多线程应用程序。:-)