C++并发多线程--死锁问题及解决方法

1--死锁问题

        死锁问题:两个线程访问资源时加锁,但都需要对方的资源才能执行释放锁;

        代码实例:下面的代码中,当线程 1 使用 my_mutex1 上锁后,会继续使用 my_mutex2 进行上锁,若此时线程 2 已经使用 my_mutex2 上锁了,也准备继续使用 my_mytex1 上锁;这时就会出现死锁问题:线程 1 掌握 my_mutex1 的资源,需要使用 my_mutex2 的资源,而线程 2 掌握 my_mutex2 的资源,需要使用 my_mutex1 的资源;两个线程都掌握各自需要的资源,导致两个线程无法继续执行,产生了死锁;

        一个简单的解决方案:将两个线程的上锁顺序设计为相同;

#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥

class myClass{
public:
    // 收集数据到消息队列
    void inMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            // 模拟收集消息
            std::cout << "Running inMsgRecvQueue(), insert one value: " 
                << i << std::endl; 

            my_mutex1.lock();
            my_mutex2.lock();
            msgRecvqueue.push_back(i); // 消息队列存储消息
            my_mutex2.unlock();
            my_mutex1.unlock();
        }
    }
    // 从消息队列取数据
    void outMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            if(!msgRecvqueue.empty()){
                my_mutex2.lock(); // 加锁
                my_mutex1.lock(); // 加锁
                // 取出数据
                int command = msgRecvqueue.front();
                msgRecvqueue.pop_front(); 

                my_mutex1.unlock(); // 解锁
                my_mutex2.unlock(); // 解锁
            }
            
            else{
                std::cout << "Running outMsgRecvQueue(), " 
                    "the msgRecvqueue is empty" << std::endl;
            }
        }
    }
private:
    std::list<int> msgRecvqueue; // 消息队列
    std::mutex my_mutex1; // 创建互斥量 
    std::mutex my_mutex2; // 创建互斥量 
};

int main(int argc, char *argv[]){
    myClass sample1;
    // 使用成员函数创建线程
    std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
    std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
    myInMsgObj.join();
    myOutMsgObj.join();

    return 0;
}

2--std::lock( )函数模板

        std::lock() 可以锁住两个或两个以上的互斥量,可以避免因为互斥量上锁顺序不同而导致的死锁问题;

        std::lock() 一般用于处理多个互斥量,其要么将多个互斥量同时锁住,要么将多个互斥量解锁(当部分互斥量未上锁时,要么等待其余互斥量上锁才继续执行,要么释放已经锁住的互斥量);

        下面的实例代码使用 std::lock() 来解决上面的死锁问题;

#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥

class myClass{
public:
    // 收集数据到消息队列
    void inMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            // 模拟收集消息
            std::cout << "Running inMsgRecvQueue(), insert one value: " 
                << i << std::endl; 

            std::lock(my_mutex1, my_mutex2);
            msgRecvqueue.push_back(i); // 消息队列存储消息
            my_mutex2.unlock();
            my_mutex1.unlock();
        }
    }
    // 从消息队列取数据
    void outMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            if(!msgRecvqueue.empty()){
                std::lock(my_mutex1, my_mutex2);
                // 取出数据
                int command = msgRecvqueue.front();
                msgRecvqueue.pop_front(); 

                my_mutex1.unlock(); // 解锁
                my_mutex2.unlock(); // 解锁
            }
            
            else{
                std::cout << "Running outMsgRecvQueue(), " 
                    "the msgRecvqueue is empty" << std::endl;
            }
        }
    }
private:
    std::list<int> msgRecvqueue; // 消息队列
    std::mutex my_mutex1; // 创建互斥量 
    std::mutex my_mutex2; // 创建互斥量 
};

int main(int argc, char *argv[]){
    myClass sample1;
    // 使用成员函数创建线程
    std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
    std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
    myInMsgObj.join();
    myOutMsgObj.join();

    return 0;
}

3--std::lock_guard( )函数模板

        std::lock_guard( ) 可以替换 mutex.lock() 和 mutex.unlock(),在项目复杂的时候可以避免遗漏上锁和解锁的问题;

        std::lock_guard<std::mutex> 对象名(互斥量, std::adopt_lock); std::adopt_lock 的作用是表明互斥量在之前已经上锁了,无需额外对互斥量进行上锁操作,只需进行解锁操作即可;

#include <iostream>
#include <thread>
#include <list>
#include <mutex> // 引入互斥

class myClass{
public:
    // 收集数据到消息队列
    void inMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            // 模拟收集消息
            std::cout << "Running inMsgRecvQueue(), insert one value: " 
                << i << std::endl; 

            std::lock(my_mutex1, my_mutex2);
            msgRecvqueue.push_back(i); // 消息队列存储消息
            std::lock_guard<std::mutex> guard1(my_mutex1, std::adopt_lock);
            std::lock_guard<std::mutex> guard2(my_mutex2, std::adopt_lock);
        }
    }
    // 从消息队列取数据
    void outMsgRecvQueue(){
        for(int i = 0; i < 100; i++){
            if(!msgRecvqueue.empty()){
                std::lock(my_mutex1, my_mutex2);
                // 取出数据
                int command = msgRecvqueue.front();
                msgRecvqueue.pop_front(); 

                std::lock_guard<std::mutex> guard1(my_mutex1, std::adopt_lock);
                std::lock_guard<std::mutex> guard2(my_mutex2, std::adopt_lock);
            }
            
            else{
                std::cout << "Running outMsgRecvQueue(), " 
                    "the msgRecvqueue is empty" << std::endl;
            }
        }
    }
private:
    std::list<int> msgRecvqueue; // 消息队列
    std::mutex my_mutex1; // 创建互斥量 
    std::mutex my_mutex2; // 创建互斥量 
};

int main(int argc, char *argv[]){
    myClass sample1;
    // 使用成员函数创建线程
    std::thread myInMsgObj(&myClass::inMsgRecvQueue, &sample1); // 收集数据线程
    std::thread myOutMsgObj(&myClass::outMsgRecvQueue, &sample1); // 取出数据线程
    myInMsgObj.join();
    myOutMsgObj.join();

    return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哲学家就餐问题是一个经典的并发编程问题,用来展示多线程编程中的和竞争条件等问题。下面是一个使用C++多线解决哲学家就餐问题的示例代码: ```c++ #include <iostream> #include <thread> #include <mutex> using namespace std; const int kNumPhilosophers = 5; // 哲学家数量 mutex forks[kNumPhilosophers]; // 叉子 void philosopher(int id) { int left_fork = id; int right_fork = (id + 1) % kNumPhilosophers; // 模拟哲学家思考 cout << "Philosopher " << id << " is thinking." << endl; this_thread::sleep_for(chrono::seconds(1)); // 尝试获得叉子 cout << "Philosopher " << id << " is hungry and wants to eat." << endl; forks[left_fork].lock(); forks[right_fork].lock(); // 开始就餐 cout << "Philosopher " << id << " is eating." << endl; this_thread::sleep_for(chrono::seconds(1)); // 释放叉子 forks[right_fork].unlock(); forks[left_fork].unlock(); // 就餐结束 cout << "Philosopher " << id << " finished eating and is thinking again." << endl; } int main() { // 创建哲学家线程 thread philosophers[kNumPhilosophers]; for (int i = 0; i < kNumPhilosophers; i++) { philosophers[i] = thread(philosopher, i); } // 等待所有哲学家线程结束 for (int i = 0; i < kNumPhilosophers; i++) { philosophers[i].join(); } return 0; } ``` 在这个示例代码中,我们使用了`mutex`来实现叉子的机制,每个哲学家线程都会先尝试获得左边和右边的叉子,如果获得成功就开始就餐,否则就会等待。这个示例代码简单易懂,但是仍然存在和竞争条件等问题,需要进一步的优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值