C++多线程异步部署推理注意事项

tensort 部署具体的joinable 线程安全管理

  • 初始状态:当一个 std::thread 对象创建并启动后,它是 joinable 的。
  • join:一旦 join 被调用,线程对象不再 joinable
  • detach:一旦 detach 被调用,线程对象也不再 joinable
  • 默认构造:默认构造的 std::thread 对象(即未关联任何线程的对象)也是不 joinable 的。

通过使用 joinable,可以确保线程的安全管理,避免不必要的错误和资源浪费。

#include <thread>
#include <iostream>
using namespace std;

void worker(int a, int& b) {
    printf("hello thread\n");
    b = 9;
}

int main() {
    // 直接启动线程
    // 注意传入参数的生命周期:
    int b = 3;
    // 下面是对引用的处理
    thread t(worker, 5, std::ref(b));

    // 等待线程结束
    // 只要线程启动,就必须 join
    // 如果没启动就 join,也会报错
    if (t.joinable()) {
        t.join();
    }

    // 输出修改后的值,验证线程是否正确地修改了传入的引用参数 b
    cout << "Value of b after thread execution: " << b << endl;

    return 0;
}
  1. 避免重复 join
    如果一个线程已经被 join 或者已经被 detach,再次调用 join 会导致程序崩溃。使用 joinable 可以确保线程在调用 join 前处于可加入状态,从而避免异常和崩溃。

  2. 线程管理
    在一些复杂的线程管理逻辑中,可能需要检查线程的状态,以决定下一步的操作。joinable 提供了一种简单的方法来检查线程是否仍然在运行或者是否已经被处理过。

  3. 资源管理
    检查线程是否 joinable 可以帮助管理资源,确保在适当的时机释放线程相关的资源。

部署防止死锁

在多线程编程中,锁(Lock)用于保护共享资源,确保同一时间只有一个线程访问共享资源,从而避免数据竞争。然而,使用锁时需要小心处理,以避免死锁(Deadlock)。以下是关于加锁、解锁以及死锁的解释和示例。

加锁和解锁

在 C++ 中,常用的锁机制包括 std::mutexstd::lock_guardstd::mutex 提供了基本的锁功能,而 std::lock_guard 是一个 RAII(Resource Acquisition Is Initialization)风格的锁管理器,确保在作用域结束时自动解锁。

示例:使用 std::mutexstd::lock_guard
#include <thread>
#include <iostream>
#include <mutex>

std::mutex mtx;

void worker(int a, int& b) {
    std::lock_guard<std::mutex> lock(mtx); // 加锁
    std::cout << "hello thread" << std::endl;
    b = 9; // 访问共享资源
    // 自动解锁(当 lock_guard 对象销毁时)
}

int main() {
    int b = 3;
    std::thread t(worker, 5, std::ref(b));

    if (t.joinable()) {
        t.join();
    }

    std::cout << "Value of b after thread execution: " << b << std::endl;

    return 0;
}

在这个例子中,std::lock_guard 在构造时自动加锁,在析构时自动解锁,确保了线程安全。

死锁

死锁是指两个或多个线程互相等待对方释放资源,从而导致程序无法继续执行。死锁通常发生在以下情况下:

  1. 互斥条件:至少有一个资源必须是非共享的,即一次只能被一个线程占用。
  2. 持有并等待条件:一个线程持有至少一个资源,并且在等待其他线程释放资源。
  3. 不剥夺条件:线程占有的资源在未使用完之前不能被剥夺,只能由占有它的线程释放。
  4. 循环等待条件:存在一个线程循环等待的链,其中每个线程都在等待下一个线程所占有的资源。
示例:可能导致死锁的代码
#include <thread>
#include <iostream>
#include <mutex>

std::mutex mtx1, mtx2;

void worker1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
    std::lock_guard<std::mutex> lock2(mtx2);
    std::cout << "Worker 1 acquired both locks" << std::endl;
}

void worker2() {
    std::lock_guard<std::mutex> lock2(mtx2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
    std::lock_guard<std::mutex> lock1(mtx1);
    std::cout << "Worker 2 acquired both locks" << std::endl;
}

int main() {
    std::thread t1(worker1);
    std::thread t2(worker2);

    t1.join();
    t2.join();

    return 0;
}

在这个例子中,worker1worker2 分别先锁定 mtx1mtx2,然后尝试锁定对方已经持有的锁,从而可能导致死锁。

避免死锁的方法

  1. 避免嵌套锁定:尽量减少嵌套锁定的情况。
  2. 使用 std::lockstd::lock 可以同时锁定多个互斥锁,避免死锁。
  3. 锁的顺序:确保所有线程以相同的顺序获取锁。
  4. 尝试锁定:使用 std::try_lock 尝试获取锁,如果不能获取则避免等待。
示例:使用 std::lock 避免死锁
#include <thread>
#include <iostream>
#include <mutex>

std::mutex mtx1, mtx2;

void worker1() {
    std::lock(mtx1, mtx2); // 同时锁定多个互斥锁
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    std::cout << "Worker 1 acquired both locks" << std::endl;
}

void worker2() {
    std::lock(mtx1, mtx2); // 同时锁定多个互斥锁
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    std::cout << "Worker 2 acquired both locks" << std::endl;
}

int main() {
    std::thread t1(worker1);
    std::thread t2(worker2);

    t1.join();
    t2.join();

    return 0;
}

在这个例子中,std::lock 同时锁定 mtx1mtx2,确保不会发生死锁。std::adopt_lock 表示 lock_guard 对象采用已经锁定的互斥锁。通过这些方法,可以有效避免死锁问题。

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值