(十三)递归锁
互斥锁的局限性
当一个函数递归调用自身时,如果在每次调用时都需要锁定一个互斥锁,那么在第二次及以后的锁定尝试中会导致死锁:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void recursive_function(int count) {
mtx.lock(); // 第一次锁定
std::cout << "Count: " << count << std::endl;
if (count > 0) {
recursive_function(count - 1); // 再次锁定,但锁已经被锁定,导致死锁
}
mtx.unlock();
}
int main() {
recursive_function(3); // 调用递归函数
return 0;
}
结果:
Count: 3 // 发生死锁
满足死锁的四个条件:
- 互斥条件:互斥锁
mtx
是非共享的,即一次只能被一个线程持有。 - 持有并等待条件:线程在第一次调用
mtx.lock()
时持有了锁。在递归调用中,线程在持有锁的同时,尝试再次锁定已经持有的锁。 - 不剥夺条件:锁
mtx
不能被强制从持有它的线程中剥夺,必须显式调用mtx.unlock()
来释放。 - 循环等待条件:线程在递归调用中形成了自循环等待,即自己在等待自己释放锁。
递归锁
递归锁允许同一线程多次锁定同一个锁,而不会导致死锁。每次加锁操作会增加锁的计数器,但不会阻塞线程;每次解锁操作会减少计数器,当计数器为零时释放锁。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
void recursive_function(int count) {
rmtx.lock(); // 第一次锁定,计数器加1
std::cout << "Count: " << count << std::endl;
if (count > 0) {
recursive_function(count - 1); // 再次锁定,计数器加1
}
rmtx.unlock(); // 解锁一次,计数器减1
}
int main() {
recursive_function(3); // 调用递归函数
return 0;
}
结果:
Count: 3
Count: 2
Count: 1
Count: 0
与互斥锁一样,递归锁同样可以使用锁管理器,例如 std::lock_guard
,std::unique_lock
,进行生命周期的自动管理。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
void recursive_function(int count) {
std::lock_guard<std::recursive_mutex> lock(rmtx); // 自动管理锁的生命周期
std::cout << "Count: " << count << std::endl;
if (count > 0) {
recursive_function(count - 1);
}
}
int main() {
recursive_function(3);
return 0;
}