我理解递归互斥锁允许多次锁定互斥锁而不会陷入死锁,并且应该解锁相同的次数。但是在什么特定情况下你需要使用递归互斥体?我正在寻找设计/代码级别的情况。
答案 0 :(得分:45)
例如,如果你有一个递归调用它的函数,并且你想获得它的同步访问:
void foo() {
... mutex_acquire();
... foo();
... mutex_release();
}
如果没有递归的互斥锁,你必须首先创建一个“入口点”函数,当你拥有一组相互递归的函数时,这会变得很麻烦。没有递归互斥:
void foo_entry() {
mutex_acquire(); foo(); mutex_release(); }
void foo() { ... foo(); ... }
答案 1 :(得分:23)
递归和非递归互斥锁具有不同的用例。没有互斥类型可以轻松替换另一个。非递归互斥体具有较少的开销,并且递归互斥体在某些情况下具有有用或甚至需要的语义,并且在其他情况下具有危险甚至破坏的语义。在大多数情况下,有人可以使用递归互斥体替换任何策略,使用基于非递归互斥体的不同更安全和更有效的策略。
答案 2 :(得分:3)
如果您想查看使用递归互斥锁的代码示例,请查看Linux / Unix的“Electric Fence”源代码。 'Twas是一种常见的Unix工具,用于在Valgrind出现之前找到“边界检查”读/写溢出和欠载以及使用已释放的内存。
只需使用源代码编译和链接电子围栏(选项-g与gcc / g ++),然后使用链接选项-lefence将其与您的软件链接,并开始逐步调用malloc / free。 http://elinux.org/Electric_Fence
答案 3 :(得分:3)
我今天遇到了递归互斥锁的需要,我认为它可能是到目前为止发布的答案中最简单的例子: 这是一个公开两个API函数的类,Process(...)和reset()。
public void Process(...)
{
acquire_mutex(mMutex);
// Heavy processing
...
reset();
...
release_mutex(mMutex);
}
public void reset()
{
acquire_mutex(mMutex);
// Reset
...
release_mutex(mMutex);
}
这两个函数不能同时运行,因为它们修改了类的内部,所以我想使用互斥锁。 问题是,Process()在内部调用reset(),并且它会产生死锁,因为已经获得了mMutex。 使用递归锁定它们可以解决问题。
答案 4 :(得分:2)
如果线程阻止尝试获取(已再次)已经拥有的互斥锁,那肯定会有问题......
是否有理由不允许同一个线程多次获取互斥锁?
答案 5 :(得分:0)
如果您希望能够从类的其他公共方法中的不同线程中调用公共方法,并且其中许多公共方法更改了对象的状态,则应使用递归互斥体。实际上,除非有充分的理由(例如出于特殊性能考虑)不使用递归互斥锁,否则我会默认使用递归互斥锁。
这会带来更好的界面,因为您不必在非锁定和锁定部分之间拆分实现,并且可以自由地在所有方法内部使用公共方法。
在我的经验中,它也使界面更易于锁定。
答案 6 :(得分:0)
总的来说,就像这里的每个人所说的那样,它更多地与设计有关。递归互斥锁通常用于递归函数中。
其他人没有告诉你的是,递归互斥锁实际上几乎没有开销。
一般来说,一个简单的互斥锁是一个 32 位的密钥,其中 0-30 位包含所有者的线程 ID,而第 31 位是一个标志,表示互斥锁是否有服务员。它有一个锁定方法,它是一种 CAS 原子竞赛,可以在失败的情况下使用系统调用来声明互斥锁。细节在这里并不重要。它看起来像这样:
class mutex {
public:
void lock();
void unlock();
protected:
uint32_t key{}; //bits 0-30: thread_handle, bit 31: hasWaiters_flag
};
recursive_mutex 通常实现为:
class recursive_mutex : public mutex {
public:
void lock() {
uint32_t handle = current_thread_native_handle(); //obtained from TLS memory in most OS
if ((key & 0x7FFFFFFF) == handle) { // Impossible to return true unless you own the mutex.
uses++; // we own the mutex, just increase uses.
} else {
mutex::lock(); // we don't own the mutex, try to obtain it.
uses = 1;
}
}
void unlock() {
// asserts for debug, we should own the mutex and uses > 0
--uses;
if (uses == 0) {
mutex::unlock();
}
}
private:
uint32_t uses{}; // no need to be atomic, can only be modified in exclusion and only interesting read is on exclusion.
};
如您所见,它完全是用户空间结构。 (但基础互斥锁不是,如果它无法在原子比较和交换锁中获取密钥,它可能会陷入系统调用,如果 has_waitersFlag 开启,它将在解锁时执行系统调用)。
对于基本互斥体实现:https://github.com/switchbrew/libnx/blob/master/nx/source/kernel/mutex.c