std::recursive_mutex 应用场景
std::recursive_mutex 是 C++ 标准库中的一种互斥锁,它允许同一个线程对其进行多次锁定而不会产生死锁。与之相对的是 std::mutex,std::mutex 不允许同一个线程对其重复锁定,否则会导致死锁。下面为你详细介绍 std::recursive_mutex 的应用场景。
在多线程环境中,std::recursive_mutex 适用于递归调用函数或嵌套成员函数调用的场景,并且由于多线程并发执行,其使用场景的特点会更加明显。下面详细介绍在多线程环境下 std::recursive_mutex 的应用场景及示例代码。
-
递归调用的函数或方法在多线程中的应用
当多个线程同时对树形结构等需要递归处理的数据进行操作时,每个线程在递归处理过程中都可能需要锁定同一个互斥锁。std::recursive_mutex 可以保证同一个线程在递归调用时不会因为重复锁定而产生死锁,同时不同线程之间仍然能保证互斥访问共享资源。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
class TreeNode {
public:
std::recursive_mutex nodeMutex;
int value;
TreeNode* left;
TreeNode* right;
TreeNode(int val) : value(val), left(nullptr), right(nullptr) {}
void processNode() {
std::lock_guard<std::recursive_mutex> lock(nodeMutex);
std::cout << "Thread " << std::this_thread::get_id()
<< " processing node with value: " << value << std::endl;
if (left) {
left->processNode();
}
if (right) {
right->processNode();
}
}
};
void threadFunction(TreeNode* node) {
node->processNode();
}
int main() {
TreeNode root(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left->left = new TreeNode(4);
std::vector<std::thread> threads;
// 创建多个线程处理树节点
for (int i = 0; i < 2; ++i) {
threads.emplace_back(threadFunction, &root);
}
// 等待所有线程完成
for (auto& thread : threads) {
thread.join();
}
delete root.left->left;
delete root.left;
delete root.right;
return 0;
}
在上述代码中,多个线程同时对树节点进行处理,每个线程在递归调用 processNode 方法时都会锁定 nodeMutex。由于使用了 std::recursive_mutex,同一个线程在递归过程中可以多次锁定该互斥锁,避免了死锁,同时不同线程之间仍然能保证对节点的互斥访问。
2、嵌套的成员函数调用在多线程中的应用
在多线程环境下,不同线程可能会同时调用类的成员函数,而这些成员函数之间存在嵌套调用关系,并且都需要访问共享资源并锁定同一个互斥锁。std::recursive_mutex 可以确保在这种情况下不会出现死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
class MyClass {
private:
std::recursive_mutex mtx;
int sharedData;
void innerFunction() {
std::lock_guard<std::recursive_mutex> lock(mtx);
std::cout << "Thread " << std::this_thread::get_id()
<< " inner function accessing shared data: " << sharedData << std::endl;
}
public:
void outerFunction() {
std::lock_guard<std::recursive_mutex> lock(mtx);
std::cout << "Thread " << std::this_thread::get_id()
<< " outer function accessing shared data: " << sharedData << std::endl;
innerFunction();
}
};
void threadFunction(MyClass& obj) {
obj.outerFunction();
}
int main() {
MyClass obj;
std::vector<std::thread> threads;
// 创建多个线程调用成员函数
for (int i = 0; i < 2; ++i) {
threads.emplace_back(threadFunction, std::ref(obj));
}
// 等待所有线程完成
for (auto& thread : threads) {
thread.join();
}
return 0;
}
在这个例子中,多个线程同时调用 MyClass 的 outerFunction 方法,outerFunction 会锁定 mtx 并调用 innerFunction,innerFunction 也需要锁定 mtx。由于使用了 std::recursive_mutex,同一个线程在嵌套调用时不会产生死锁,不同线程之间也能保证对共享资源的互斥访问。
注意事项
- 性能开销:
std::recursive_mutex相较于std::mutex有更高的性能开销,因为它需要记录锁定和解锁的次数,以允许同一个线程的递归锁定。所以在设计多线程程序时,应尽量避免不必要的递归锁定,优先考虑使用std::mutex。 - 代码复杂度:使用
std::recursive_mutex可能会增加代码的复杂度,掩盖潜在的设计问题。在使用之前,应仔细评估是否真的需要递归锁定,是否可以通过重构代码来避免这种情况。
std::mutex 应用场景
如果不使用 std::recursive_mutex,可以通过以下几种方式来实现类似的功能,下面结合之前的两个示例分别介绍具体的实现方法。
1. 对于递归调用的函数或方法场景
在树形结构遍历的场景中,若不使用 std::recursive_mutex,可以通过重新设计代码逻辑,将递归调用改为迭代调用,同时使用 std::mutex 来保证线程安全。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <stack>
class TreeNode {
public:
std::mutex nodeMutex;
int value;
TreeNode* left;
TreeNode* right;
TreeNode(int val) : value(val), left(nullptr), right(nullptr) {}
};
void processNode(TreeNode* root) {
if (!root) return;
std::stack<TreeNode*> nodeStack;
nodeStack.push(root);
while (!nodeStack.empty()) {
TreeNode* current = nodeStack.top();
nodeStack.pop();
{
std::lock_guard<std::mutex> lock(current->nodeMutex);
std::cout << "Thread " << std::this_thread::get_id()
<< " processing node with value: " << current->value << std::endl;
}
if (current->right) nodeStack.push(current->right);
if (current->left) nodeStack.push(current->left);
}
}
void threadFunction(TreeNode* node) {
processNode(node);
}
int main() {
TreeNode root(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left->left = new TreeNode(4);
std::vector<std::thread> threads;
// 创建多个线程处理树节点
for (int i = 0; i < 2; ++i) {
threads.emplace_back(threadFunction, &root);
}
// 等待所有线程完成
for (auto& thread : threads) {
thread.join();
}
delete root.left->left;
delete root.left;
delete root.right;
return 0;
}
代码解释
- 这里使用
std::stack来模拟递归调用的栈,将递归遍历改为迭代遍历。 - 在每次处理节点时,使用
std::lock_guard<std::mutex>来锁定当前节点的nodeMutex,保证线程安全。 - 这样就避免了递归调用中重复锁定同一个互斥锁的问题,从而可以使用
std::mutex代替std::recursive_mutex。
2. 对于嵌套的成员函数调用场景
对于嵌套成员函数调用的场景,可以将嵌套调用的逻辑进行拆分,减少重复锁定的情况。例如,将共享资源的访问逻辑提取到一个单独的函数中,只在这个函数中进行锁定操作。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
class MyClass {
private:
std::mutex mtx;
int sharedData;
void accessSharedData() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Thread " << std::this_thread::get_id()
<< " accessing shared data: " << sharedData << std::endl;
}
public:
void outerFunction() {
accessSharedData();
}
void innerFunction() {
accessSharedData();
}
};
void threadFunction(MyClass& obj) {
obj.outerFunction();
obj.innerFunction();
}
int main() {
MyClass obj;
std::vector<std::thread> threads;
// 创建多个线程调用成员函数
for (int i = 0; i < 2; ++i) {
threads.emplace_back(threadFunction, std::ref(obj));
}
// 等待所有线程完成
for (auto& thread : threads) {
thread.join();
}
return 0;
}
代码解释
- 定义了一个
accessSharedData函数,专门用于访问共享资源,并在该函数中使用std::lock_guard<std::mutex>进行锁定操作。 outerFunction和innerFunction都调用accessSharedData来访问共享资源,避免了在嵌套调用中重复锁定同一个互斥锁的问题,从而可以使用std::mutex保证线程安全。
通过以上两种方式,在不使用 std::recursive_mutex 的情况下,同样可以实现多线程环境下的线程安全,并且避免了 std::recursive_mutex 带来的额外性能开销和代码复杂度。
2892

被折叠的 条评论
为什么被折叠?



