C++ 多线程中递归调用分别使用recursive_mutex和std::mutex的使用场景

std::recursive_mutex 应用场景

   std::recursive_mutex 是 C++ 标准库中的一种互斥锁,它允许同一个线程对其进行多次锁定而不会产生死锁。与之相对的是 std::mutexstd::mutex 不允许同一个线程对其重复锁定,否则会导致死锁。下面为你详细介绍 std::recursive_mutex 的应用场景。

        在多线程环境中,std::recursive_mutex 适用于递归调用函数或嵌套成员函数调用的场景,并且由于多线程并发执行,其使用场景的特点会更加明显。下面详细介绍在多线程环境下 std::recursive_mutex 的应用场景及示例代码。

  1. 递归调用的函数或方法在多线程中的应用

        当多个线程同时对树形结构等需要递归处理的数据进行操作时,每个线程在递归处理过程中都可能需要锁定同一个互斥锁。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 并调用 innerFunctioninnerFunction 也需要锁定 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 带来的额外性能开销和代码复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼儿v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值