jas*_*ine 56 c++ recursion multithreading mutex recursive-mutex
我理解递归互斥锁允许互斥锁被锁定不止一次而不会陷入死锁,应该解锁相同的次数.但是在什么特定情况下你需要使用递归互斥体?我在寻找设计/代码级别的情况.
Ant*_*ima 48
例如,当您具有递归调用它的函数,并且您希望获得对它的同步访问:
void foo() {
... mutex_acquire();
... foo();
... mutex_release();
}
Run Code Online (Sandbox Code Playgroud)
如果没有递归互斥锁,则必须首先创建一个"入口点"函数,当您拥有一组相互递归的函数时,这会变得很麻烦.没有递归互斥:
void foo_entry() {
mutex_acquire(); foo(); mutex_release(); }
void foo() { ... foo(); ... }
Run Code Online (Sandbox Code Playgroud)
com*_*nad 23
递归和非递归互斥锁具有不同的用例.没有互斥类型可以轻松替换另一个.非递归互斥体具有较少的开销,并且递归互斥体在某些情况下具有有用或甚至需要的语义,并且在其他情况下具有危险甚至破坏的语义.在大多数情况下,有人可以使用递归互斥体替换任何策略,使用基于非递归互斥体的不同更安全和更有效的策略.
我今天遇到了递归互斥锁的需求,我认为这可能是迄今为止发布的答案中最简单的例子:这是一个公开两个 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);
}
Run Code Online (Sandbox Code Playgroud)
这两个函数不能同时运行,因为它们修改了类的内部结构,所以我想使用互斥锁。问题是,Process() 在内部调用 reset(),它会造成死锁,因为 mMutex 已经被获取。用递归锁锁定它们可以解决问题。
总的来说,正如这里的每个人所说,更多的是设计。递归互斥体通常用在递归函数中。
其他人没有在这里告诉您的是,递归互斥体实际上几乎没有成本开销。
一般来说,简单的互斥锁是一个 32 位密钥,其中第 0-30 位包含所有者的线程 ID,第 31 位是表示互斥锁是否有等待者的标志。它有一个 lock 方法,该方法是 CAS 原子竞赛,用于在失败时通过系统调用声明互斥锁。细节在这里并不重要。它看起来像这样:
class mutex {
public:
void lock();
void unlock();
protected:
uint32_t key{}; //bits 0-30: thread_handle, bit 31: hasWaiters_flag
};
Run Code Online (Sandbox Code Playgroud)
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.
};
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,它完全是一个用户空间构造。(但是基本互斥体不是,如果它无法在原子比较和交换锁定中获取密钥,它可能会陷入系统调用,并且如果 has_waitersFlag 处于打开状态,它将在解锁时执行系统调用)。
对于基本互斥体实现:https://github.com/switchbrew/libnx/blob/master/nx/source/kernel/mutex.c