C++ 怎么解决死锁问题

news/2024/9/14 13:16:02 标签: c++, 开发语言

什么是死锁

当多个线程以不同的顺序锁定相同的多个互斥锁时,可能会导致死锁。这是因为一个线程可能会持有一个互斥锁,并等待另一个线程持有的锁,而第二个线程则持有第二个锁并等待第一个线程持有的锁,从而导致循环等待。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m1;
std::mutex m2;

void thread1() {
    std::lock_guard<std::mutex> lck1(m1);
    std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟一些工作
    std::lock_guard<std::mutex> lck2(m2);
    std::cout << "Thread 1 completed" << std::endl;
}

void thread2() {
    std::lock_guard<std::mutex> lck2(m2);
    std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟一些工作
    std::lock_guard<std::mutex> lck1(m1);
    std::cout << "Thread 2 completed" << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);

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

    return 0;
}

在这个示例中,thread1 先锁定 m1 再锁定 m2,而 thread2 则先锁定 m2 再锁定 m1。如果 thread1 持有 m1 并等待 m2,而 thread2 同时持有 m2 并等待 m1,这将导致死锁。 

 

可以看出程序在编译成功之后没有任何反应,而是“暂停”了。

 解决方法 1:使用 std::lock

C++11 提供了 std::lock 函数,该函数可以一次性锁定多个互斥锁,并确保不会发生死锁。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m1;
std::mutex m2;

void thread1() {
    std::lock(m1, m2); // 同时锁定 m1 和 m2
    std::lock_guard<std::mutex> lck1(m1, std::adopt_lock); // 告诉 lock_guard 互斥锁已经被锁定
    std::lock_guard<std::mutex> lck2(m2, std::adopt_lock);
    std::cout << "Thread 1 completed" << std::endl;
}

void thread2() {
    std::lock(m1, m2); // 保持锁定顺序一致
    std::lock_guard<std::mutex> lck1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> lck2(m2, std::adopt_lock);
    std::cout << "Thread 2 completed" << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);

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

    return 0;
}

解决方法 2:使用 std::scoped_lock (C++17)

C++17 引入了 std::scoped_lock,它更简洁地解决了多互斥锁的并发问题,并且自动处理锁定和解锁。std::scoped_lock 可以同时锁定多个互斥锁,确保不会发生死锁。 

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m1;
std::mutex m2;

void thread1() {
    std::scoped_lock lck(m1, m2); // 同时锁定 m1 和 m2
    std::cout << "Thread 1 completed" << std::endl;
}

void thread2() {
    std::scoped_lock lck(m1, m2); // 锁定顺序无关紧要
    std::cout << "Thread 2 completed" << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);

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

    return 0;
}

第二种情况的死锁 

场景:在持有锁的时候,调用未知代码。如果不了解代码做了什么,就有死锁的风险。

void do_this(Foo* p)
{
lock_guard<mutex> lck {my_mutex};
// ... 做一 些 事 ...
p->act(my_data);
// ...
}

如果不知道 Foo::act 会干什么(可能它是一个虚函数,调用某个还未编写的某个派生类成员),它可能会(递归地)调用 do_this 因而在 my_mutex 上造成死锁。 可能它会在某个别的 mutex上锁定而无法在适当的时间内返回,对任何调用了 do_this 的代码造成延迟。​​​​​​​

解决方法:在lock_guard中用 recursive_mutex!

第三种情况的死锁

场景:在当前线程持有锁的时候挂起协程。

这种模式会导致明显的死锁风险。某些种类的等待允许当前线程在异步操作完成前实施一些额外的工作。如果持有锁的线程实施了需要相同的所的工作,那它就会因为试图获取它已经持有的锁而发生死锁

如果协程在某个与获得锁的线程不同的另一个线程中完成,那就是未定义行为。即使协程中明确返回其原来的线程,仍然有可能在协程恢复之前抛出异常,并导致其锁定防护对象(lock_guard)未能销毁。

解决方法:

std::mutex g_lock;
std::future<void> Class::do_something()
{
    {
    std::lock_guard<std::mutex> guard(g_lock);
    // 修改被锁保护的数据
    }
    co_await something(); // OK:锁 已 经 在 协 程 挂 起 前 被 释 放
    co_await somethingElse();
}

这种模式对于性能也不好。每当遇到比如 co_await 这样的挂起点时,都会停止当前函数的执行而去运行别的代码。而在协程恢复之前可能会经过很长时间。这个锁会在整个时间段中持有,并且无法被其他线程获得以进行别的工作。


http://www.niftyadmin.cn/n/5561851.html

相关文章

在微服务架构中如何使用 Dubbo 和 Feign 进行服务调用?

假设我们有一个电商系统&#xff0c;其中包含两个微服务&#xff1a;ProductService&#xff08;商品服务&#xff09;和 OrderService&#xff08;订单服务&#xff09;。OrderService 需要调用 ProductService 来获取商品信息。 使用 Dubbo 进行服务调用 #### 1. 定义服务接…

C语言:键盘录入案例

主要使用了scanf&#xff1b; scanf的使用方法和注意事项&#xff1a; 1.作用&#xff1a; 用于接收键盘输入的数据并赋值给对应的变量 2.使用方式; scanf("占位符",&变量名); 3.注意事项; 占位符后面的的变量要对应 第一个参数中不写换行 案例1&#xf…

LLM:学习清单 ing

根据模型的数据流程方向和自己的经验列出&#xff1a; 一、模型输入 分词器&#xff1a;BPE&#xff0c;BBPE 位置编码&#xff1a;绝对位置编码&#xff0c;三角函数编码&#xff0c;ROPE 词向量模型&#xff1a;词袋&#xff0c;监督学习模型&#xff1b;BGE&#xff0c;BC…

从0开始的STM32HAL库学习2

外部中断(HAL库GPIO讲解) 今天我们会详细地学习STM32CubeMX配置外部中断&#xff0c;并且讲解HAL库的GPIO的各种函数。 准备工作&#xff1a; 1、STM32开发板&#xff08;我的是STM32F103C8T6&#xff09; 2、STM32CubeMx软件、 IDE&#xff1a; Keil软件 3、STM32F1xx/ST…

httpx 的使用

httpx 是一个可以支持 HTTP/2.0 的库 还有一个是&#xff1a; hyper 库 这里有一个由HTTP/2.0的网站&#xff1a; https://spa16.scrape.center/ 使用 requests 库 进行爬取 import requests url https://spa16.scrape.center/ response requests.get(url) print(response…

【鸿蒙OS】【ArkUI】鸿蒙OS UI布局适配终极攻略

鸿蒙OS UI布局适配终极攻略 像素适配大法&#xff0c;此方法也适合Android ArkUI为开发者提供4种像素单位&#xff0c;框架采用vp为基准数据单位。 vp相当于Android里的dp fp相当于Android里的sp 官方是如何定义的呢,如下图 今天我来教大家如何用PX做到ArkUI的终级适配&…

力扣刷题之2959.关闭分部的可行集合数目

题干描述 一个公司在全国有 n 个分部&#xff0c;它们之间有的有道路连接。一开始&#xff0c;所有分部通过这些道路两两之间互相可以到达。 公司意识到在分部之间旅行花费了太多时间&#xff0c;所以它们决定关闭一些分部&#xff08;也可能不关闭任何分部&#xff09;&…

解锁Vue警报的迷雾:攻克“TypeError: Cannot read property ‘getAttribute’ of null”的奥秘

在Vue.js的广阔天地里&#xff0c;开发者们常常会遇到各式各样的挑战与“小惊喜”。[Vue warn]: Error in mounted hook: “TypeError: Cannot read property ‘getAttribute’ of null”这一错误&#xff0c;就像是一位不速之客&#xff0c;冷不防地在你的代码世界中留下一串令…