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 带来的额外性能开销和代码复杂度。

<think> 我们被要求解释代码 `std::lock_guard<std::recursive_mutex> lock(le_mutex_);` 的含义。 根据引用[1]中提供的示例,我们看到了 `std::lock_guard` 与 `std::mutex` 的使用,其作用是实现互斥锁的自动管理。 此外,我们注意到这里使用的是 `std::recursive_mutex`(递归互斥量)而不是普通的 `std::mutex`。 解释如下: 1. `std::lock_guard` 是一个模板类,用于管理一个互斥量的锁定解锁。它在构造时获取互斥量的锁,在析构时自动释放锁。这可以保证即使代码块中发生异常,锁也能被正确释放,从而避免死锁。 2. 这里使用的互斥量类型是 `std::recursive_mutex`(递归互斥量)。递归互斥量允许同一个线程多次获取同一个锁,而不会导致死锁。这对于可能被同一个线程多次调用的函数(例如递归函数)或同一个线程中嵌套调用的函数非常有帮助。 3. 变量 `le_mutex_` 是一个 `std::recursive_mutex` 类型的互斥量对象。这行代码的作用是在当前作用域内,构造一个 `std::lock_guard` 对象,该对象会锁定 `le_mutex_` 这个递归互斥量。当这个 `lock` 对象离开作用域时(例如函数返回或代码块结束),其析构函数会自动解锁互斥量。 因此,这行代码的含义是:使用 `std::lock_guard` 自动管理 `le_mutex_` 这个递归互斥量的锁定解锁,确保当前作用域内的代码执行时拥有该互斥量的锁,并且在离开作用域时自动释放锁。 与引用[1]中的示例比较: 引用[1]使用了普通互斥量 `std::mutex`,而这里使用递归互斥量。递归互斥量在同一个线程内可以多次加锁,每次加锁计数器加1,解锁时计数器减1,直到计数器为0时其他线程才能获取锁。 举例说明: 假设有两个函数AB,它们都需要对同一个互斥量加锁,并且函数A调用了函数B。如果使用普通互斥量,那么当函数A已经获得锁的情况下,函数B再尝试获得同一个锁就会导致死锁(因为同一个线程不能重复获得同一个普通互斥量)。而使用递归互斥量则允许这种情况,因为同一个线程可以多次获得同一个递归互斥量。 因此,当我们看到 `std::lock_guard<std::recursive_mutex>` 时,可以推断该代码段可能被同一个线程重入,或者设计上需要允许递归锁的情况。 注意:递归互斥量通常比普通互斥量开销大,所以只在必要时使用。 根据引用[2]引用[3]的内容,我们并没有直接关于递归互斥量的使用,但它们展示了多线程编程中锁的使用模块初始化过程。不过,我们的解释已经足够。 相关问题: 1. 递归互斥量(`std::recursive_mutex`)与普通互斥量(`std::mutex`)有哪些区别? 2. 在什么情况下应该使用递归互斥量? 3. 使用 `std::lock_guard` 管理锁相比手动调用 `lock()` `unlock()` 有什么优势? 4. 递归互斥量会带来哪些性能开销? 请参考以上解释相关问题。 </think> ### 解释 `std::lock_guard<std::recursive_mutex> lock(le_mutex_);` 这段代码是 C++ 中用于线程安全的互斥锁管理机制,核心含义是**通过 RAII(资源获取即初始化)技术实现对递归互斥量的自动加锁解锁**。具体分解如下: 1. **`std::recursive_mutex`(递归互斥量)** - 这是 C++ 标准库提供的锁类型,特点是**允许同一个线程多次获取同一个锁**。 - 例如:在递归函数或嵌套调用中,线程已持有锁时再次请求同一锁不会导致死锁。 - 每次加锁时内部计数器 `+1`,解锁时 `-1`,直到计数器为 `0` 时其他线程才能获取锁[^1]。 2. **`lock_guard` 的作用** - `lock_guard` 是轻量级 RAII 封装类,**在构造时自动加锁**,**在析构时(离开作用域时)自动解锁**。 - 例如: ```cpp { std::lock_guard<std::recursive_mutex> lock(le_mutex_); // 构造时加锁 // 临界区代码(受保护的操作) } // 离开作用域时自动解锁 ``` - 优势:避免手动调用 `lock()`/`unlock()`,确保异常安全,防止死锁[^1]。 3. **整体行为** - 创建名为 `lock` 的 `lock_guard` 对象,将 `le_mutex_`(递归互斥量)作为参数传入。 - 对象生命周期内持有锁,保障当前线程独占访问共享资源(如全局变量、数据结构)。 - 适用于需要**线程安全且可能递归调用**的场景(如事件回调嵌套处理)[^2]。 ### 与其他锁的对比 | 类型 | 特点 | 适用场景 | |---------------------|----------------------------------|----------------------------| | `std::mutex` | 同一线程不可重复加锁 | 简单临界区 | | `std::recursive_mutex` | 允许同一线程重复加锁 | 递归函数/嵌套调用 | | `std::lock_guard` | RAII 自动管理,不支持手动解锁 | 作用域明确的资源保护 | ### 实际应用参考 在引用[2]的蓝牙事件处理中,类似机制可能用于保护 `subevent_handlers_` 映射表,确保注册/调用事件回调时的线程安全: ```cpp // 伪代码示例 void register_event(EventType event, Callback handler) { std::lock_guard<std::recursive_mutex> lock(le_mutex_); // 保护共享资源 handlers_[event] = handler; // 安全操作映射表 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

摸鱼儿v

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

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

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

打赏作者

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

抵扣说明:

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

余额充值