只要从同一个线程进行相同数量的解锁调用,就可以从单个线程多次锁定递归互斥锁,而无需解锁。当一个共享资源被多个函数使用,并且其中一个函数调用另一个使用该资源的函数时,这种机制会派上用场。
考虑以下类:
class Foo {
public:
Foo();
void bar(); // Does something to the resource
void thud(); // Calls bar() then does something else to the resource
private:
Resource mRes;
QMutex mLock;
}
初始实现可能如下所示:
Foo::Foo() {}
void Foo::bar() {
QMutexLocker locker(&mLock);
mRes.doSomething();
}
void Foo::thud() {
QMutexLocker locker(&mLock);
bar();
mRes.doSomethingElse();
}
上面的代码在调用 thud 时会死锁。 mLock 将在 thud() 的第一行获取,然后再次由 bar() 的第一行获取,这将阻塞等待 thud() 释放锁。
一个简单的解决方案是使锁在 ctor 中递归。
Foo::Foo() : mLock(QMutex::Recursive) {}
这是一个不错的修复,适用于许多情况,但是应该注意,使用此解决方案可能会降低性能,因为每个递归互斥锁调用可能需要系统调用来识别当前线程 ID。
除了线程id检查之外,所有对thud()的调用还是会执行QMutex::lock()两次!
需要递归的设计可以被重构以消除对递归互斥体的需要。一般来说,对递归互斥体的需求是一种“代码味道”,表明需要遵守关注点分离原则。
对于 Foo 类,可以想象创建一个私有函数调用来执行共享计算并将资源锁定保持在公共接口级别。
class Foo {
public:
Foo();
void bar(); // Does something to the resource
void thud(); // Does something then does something else to the resource
private:
void doSomething();
private:
Resource mRes;
QMutex mLock;
}
Foo::Foo() {}
// public
void Foo::bar() {
QMutexLocker locker(&mLock);
doSomething();
}
void Foo::thud() {
QMutexLocker locker(&mLock);
doSomething();
mRes.doSomethingElse();
}
// private
void Foo::doSomething() {
mRes.doSomething(); // Notice - no mutex in private function
}