深入探讨多线程编程:从0-1为您解释多线程(下)

文章目录

  • 6. 死锁
    • 6.1 死锁
      • 原因
    • 6.2 避免死锁的方法
      • 加锁顺序一致性。
      • 超时机制。
      • 死锁检测和解除机制。

6. 死锁

6.1 死锁

原因

  1. 系统资源的竞争:(产生环路)当系统中供多个进程共享的资源数量不足以满足进程的需要时,会引起进程对2资源的竞争而产生死锁。例如,两个进程分别持有资源R1和R2,但进程1申请资源R2,进程2申请资源R1时,两者都会因为所需资源被占用而阻塞。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex resourceR1, resourceR2;

bool acquireResource(std::timed_mutex& r, const std::string& name) {
    std::chrono::milliseconds timeout(5000);  // 5秒超时
    if (r.try_lock_for(timeout)) {
        std::cout << "Process " << name << " has acquired its resource." << std::endl;
        return true;
    }
    else {
        std::cout << "Process " << name << " failed to acquire the resource within 5 seconds. Terminating..." << std::endl;
        return false;
    }
}

void process1() {
    if (acquireResource(resourceR1, "1")) {
        // 如果成功获取资源R1,尝试获取资源R2
        if (!acquireResource(resourceR2, "1")) {
            // 若获取资源R2失败,解锁资源R1并终止线程
            resourceR1.unlock();
            return;
        }

        /********************************************************/
        //需要执行的业务逻辑(不会被执行)
        /********************************************************/

        resourceR1.unlock();
        resourceR2.unlock();
    }
}

void process2() {
    if (acquireResource(resourceR2, "2")) {
        if (!acquireResource(resourceR1, "2")) {
            resourceR2.unlock();
            return;
        }

        // 同样,此处的业务逻辑也不会被执行
        resourceR1.unlock();
        resourceR2.unlock();
    }
}

int main() {
    std::thread t1(process1);
    std::thread t2(process2);

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

    return 0;
}

在这里插入图片描述
在这里插入图片描述

  1. 逻辑错误:程序逻辑错误可能导致死锁,如死循环或无限等待的情况。例如,在数据交换中,如果一方发送的消息丢失,发送方会等待接收返回信息,而接收方会无限等待接收信息,导致死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex mtx;
std::condition_variable cv1, cv2;
bool messageReceived = false;
bool acknowledgmentSent = false;

// 发送线程
void senderThread() {
    std::cout << "Sender: Sending data...\n";
    // 假设发送数据(此处省略具体发送逻辑)

    std::unique_lock<std::mutex> lk(mtx);
    auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(5);
    while (!acknowledgmentSent && std::cv_status::timeout == cv1.wait_until(lk, timeout)) {
        if (std::chrono::system_clock::now() >= timeout) {
            std::cout << "Sender: Timeout occurred, assuming no acknowledgement received and exiting.\n";
            break;  // 超时后退出循环,不再等待确认
        }
    }
    lk.unlock();
}

// 接收线程
void receiverThread() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 假设在此期间消息丢失

    std::unique_lock<std::mutex> lk(mtx);
    std::cout << "Receiver: Received data...\n";
    messageReceived = true;
    cv2.notify_one();  // 假设这是接收方发送确认的方式

    // 接收方也会等待发送方确认收到确认信息(这是一个逻辑错误,实际应用中通常不需要)
    auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(5);
    while (!messageReceived && std::cv_status::timeout == cv2.wait_until(lk, timeout)) {
        if (std::chrono::system_clock::now() >= timeout) {
            std::cout << "Receiver: Timeout occurred, assuming message not delivered and exiting.\n";
            break;  // 超时后退出循环,不再等待消息
        }
    }
    lk.unlock();
}

int main() {
    std::thread t1(senderThread);
    std::thread t2(receiverThread);

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

    return 0;
}

在这里插入图片描述
两秒后
在这里插入图片描述
五秒后
在这里插入图片描述

  1. 不恰当的同步:在并发编程中,不恰当的同步机制可能导致死锁。例如,多个线程在等待其他线程释放锁时,如果这些线程彼此都持有对方需要的锁,就会导致死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex mtx1, mtx2;

void threadFunction1() {
    if (mtx1.try_lock_for(std::chrono::seconds(5))) {
        std::cout << "Thread 1: Acquired mtx1\n";

        // 尝试获取mtx2,如果5秒内未获取成功,则释放mtx1以防止死锁
        if (!mtx2.try_lock_for(std::chrono::seconds(5))) {
            mtx1.unlock();
            std::cout << "Thread 1: Could not acquire mtx2 within 5 seconds, releasing mtx1 to prevent deadlock.\n";
            return;
        }

        std::cout << "Thread 1: Acquired mtx2\n";
        mtx2.unlock();
        mtx1.unlock();
    }
    else {
        std::cout << "Thread 1: Could not acquire mtx1 within 5 seconds.\n";
    }
}

void threadFunction2() {
    if (mtx2.try_lock_for(std::chrono::seconds(5))) {
        std::cout << "Thread 2: Acquired mtx2\n";

        if (!mtx1.try_lock_for(std::chrono::seconds(5))) {
            mtx2.unlock();
            std::cout << "Thread 2: Could not acquire mtx1 within 5 seconds, releasing mtx2 to prevent deadlock.\n";
            return;
        }

        std::cout << "Thread 2: Acquired mtx1\n";
        mtx1.unlock();
        mtx2.unlock();
    }
    else {
        std::cout << "Thread 2: Could not acquire mtx2 within 5 seconds.\n";
    }
}

int main() {
    std::thread t1(threadFunction1);
    std::thread t2(threadFunction2);

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

    return 0;
}

在这里插入图片描述
在这里插入图片描述

6.2 避免死锁的方法

加锁顺序一致性。

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

std::mutex mtx1, mtx2;

// 定义一个固定的全局锁顺序
const bool lockOrder[] = {true, false}; // 先锁mtx1,后锁mtx2

void worker(int id) {
    if (lockOrder[0]) {
        mtx1.lock();
        std::cout << "Thread " << id << ": Acquired mtx1\n";

        // 在拥有mtx1的情况下尝试获取mtx2
        mtx2.lock();
        std::cout << "Thread " << id << ": Acquired mtx2\n";
    } else {
        // 如果定义的顺序是先锁mtx2
        mtx2.lock();
        std::cout << "Thread " << id << ": Acquired mtx2\n";

        // 在拥有mtx2的情况下尝试获取mtx1
        mtx1.lock();
        std::cout << "Thread " << id << ": Acquired mtx1\n";
    }

    // 重要:解锁按照相反的顺序进行
    mtx2.unlock();
    mtx1.unlock();

    // 业务逻辑...
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

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

    return 0;
}

在上述示例中,我们预定义了一个全局的锁获取顺序数组lockOrder,确保所有线程按照同样的顺序(本例中是先获取mtx1再获取mtx2)来获取互斥锁。这样可以防止如下情况:一个线程持有mtx1并等待mtx2,而另一个线程持有mtx2并等待mtx1,从而形成死锁。

请注意,为了避免死锁,不仅在获取锁时需遵循一致的顺序,而且在解锁时也应按照相反的顺序进行。在上面的代码中,无论哪种顺序,我们都是先解锁mtx2,然后再解锁mtx1。这样可以确保在任何时候,已经持有两个锁的线程都能顺利地按顺序释放它们,避免死锁的发生。

超时机制。

以下是一个使用std::timed_mutex的示例,当尝试获取互斥锁时设置一个超时时间,如果在规定时间内没能获取到锁,则线程放弃获取,从而可以避免死锁的发生:

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

std::timed_mutex mtx1, mtx2;

void worker(int id) {
    if (id == 1) {
        // 线程1尝试获取mtx1
        if (mtx1.try_lock_for(std::chrono::seconds(5))) {
            std::cout << "Thread " << id << ": Acquired mtx1\n";

            // 在持有mtx1的前提下尝试获取mtx2,超时时间为5秒
            if (mtx2.try_lock_for(std::chrono::seconds(5))) {
                std::cout << "Thread " << id << ": Acquired mtx2\n";
                mtx2.unlock();
            } else {
                std::cout << "Thread " << id << ": Could not acquire mtx2 within 5 seconds, releasing mtx1.\n";
            }
            mtx1.unlock();
        } else {
            std::cout << "Thread " << id << ": Could not acquire mtx1 within 5 seconds.\n";
        }
    } else if (id == 2) {
        // 线程2尝试获取mtx2,同样设置5秒超时
        if (mtx2.try_lock_for(std::chrono::seconds(5))) {
            std::cout << "Thread " << id << ": Acquired mtx2\n";

            // 在持有mtx2的前提下尝试获取mtx1,同样设置5秒超时
            if (mtx1.try_lock_for(std::chrono::seconds(5))) {
                std::cout << "Thread " << id << ": Acquired mtx1\n";
                mtx1.unlock();
            } else {
                std::cout << "Thread " << id << ": Could not acquire mtx1 within 5 seconds, releasing mtx2.\n";
            }
            mtx2.unlock();
        } else {
            std::cout << "Thread " << id << ": Could not acquire mtx2 within 5 seconds.\n";
        }
    }
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

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

    return 0;
}

在这个示例中,两个线程都试图按顺序获取互斥锁,但是如果在5秒钟内无法获取所需的下一个锁,它们都会释放已经持有的锁并退出相应的操作,从而避免了死锁的发生。

死锁检测和解除机制。

在C++标准库中并没有内置的死锁检测和解除机制,但我们可以通过设计良好的程序逻辑和利用特定的同步原语(如条件变量、互斥量等)来实施自己的死锁检测和解除策略。

// 假设有以下结构表示资源和进程的状态
struct Process {
    int pid; // 进程ID
    std::vector<int> holdingResources; // 当前持有的资源ID集合
    std::vector<int> requestingResources; // 正在请求的资源ID集合
};

struct Resource {
    int rid; // 资源ID
    int available; // 当前可用的数量
    std::map<int, int> allocated; // 已分配给各个进程的资源数量
};

// 假设有个全局的数据结构存储所有进程和资源的状态
std::vector<Process> processes;
std::vector<Resource> resources;

// 自定义的死锁检测函数(伪代码)
bool detectAndResolveDeadlocks() {
    // 初始化资源分配图(Resource Allocation Graph, RAG)
    // ...

    // 检查是否有循环等待
    for (auto& p : processes) {
        // 使用拓扑排序或其他方法检查是否存在环路
        if (isCycleDetectedInRAG(p)) {
            // 死锁检测出环,现在需要解除死锁
            resolveDeadlock(p.pid);
            return true;
        }
    }

    return false; // 没有发现死锁
}

// 解除死锁的策略有很多种,以下是一个简化的版本(仅作示例)
void resolveDeadlock(int pid) {
    // 可以选择一个进程撤销其部分请求或者抢占它的资源
    // 例如,选择持有最多资源但请求未满足最多的进程,释放其最少的一个资源
    Process& victim = getVictimProcess(pid);
    int resourceToRelease = getResourceToRelease(victim);
    
    // 释放资源并重新开始检测
    releaseResource(victim, resourceToRelease);
    victim.requestingResources.erase(
        std::find(victim.requestingResources.begin(), victim.requestingResources.end(), resourceToRelease));
}

// ... 其他辅助函数(getVictimProcess, getResourceToRelease, releaseResource等)

在实践中,死锁检测和解除往往涉及到复杂的算法和策略,比如银行家算法等。在C++程序中实现这样的功能通常需要自定义数据结构和算法,并且考虑到并发环境下的安全性,还需要适当使用锁来保护共享数据。

由于C++标准库提供的互斥量和条件变量等工具不具备自动死锁检测和解除功能,开发者需要自行设计和实现适合项目需求的死锁预防、检测及解除方案。在某些高级并发库中,可能提供了更高级别的抽象帮助处理这类问题,但C++标准库本身不直接提供这样的机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/498458.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机图形学】3D Implicit Transporter for Temporally Consistent Keypoint Discovery

对3D Implicit Transporter for Temporally Consistent Keypoint Discovery的简单理解 文章目录 1. 现有方法限制和文章改进2. 方法2.1 寻找时间上一致的3D特征点2.1.1 3D特征Transporter2.1.2 几何隐式解码器2.1.3 损失函数 2.2 使用一致特征点的操纵 1. 现有方法限制和文章改…

Swagger 文档工具 设计、构建、文档化和使用您的 RESTful API

Swagger Swagger 是一个功能强大的开源框架&#xff0c;支持大量工具生态系统&#xff0c;帮助您设计、构建、文档化和使用您的 RESTful API。 使用 SpringBoot 您可以从 swagger-springboot 获取完整的项目演示。 springboot-blog 中文版 文件结构可能如下所示&#xff1a;…

基于多模态信息的语音处理(misp) 2023挑战:视听目标说话人提取

THE MULTIMODAL INFORMATION BASED SPEECH PROCESSING (MISP) 2023 CHALLENGE: AUDIO-VISUAL TARGET SPEAKER EXTRACTION 第二章 目标说话人提取之《基于多模态信息的语音处理(misp) 2023挑战:视听目标说话人提取》 文章目录 THE MULTIMODAL INFORMATION BASED SPEECH PROCESS…

Synchronized锁、公平锁、悲观锁乐观锁、死锁等

悲观锁 认为自己在使用数据的时候一定会有别的线程来修改数据,所以在获取数据前会加锁,确保不会有别的线程来修改 如: Synchronized和Lock锁 适合写操作多的场景 乐观锁 适合读操作多的场景 总结: 线程8锁🔐 调用 声明 结果:先打印发送短信,后打印发送邮件 结论…

FPGA 图像边缘检测(Canny算子)

1 顶层代码 timescale 1ns / 1ps //边缘检测二阶微分算子&#xff1a;canny算子module image_canny_edge_detect (input clk,input reset, //复位高电平有效input [10:0] img_width,input [ 9:0] img_height,input [ 7:0] low_threshold,input [ 7:0] high_threshold,input va…

uniapp 中引入第三方组件后,更改组件的样式 -使用/deep/不生效

在我们使用Vue搭建项目的时候&#xff0c;我们经常会用到一些UI框架&#xff0c;如Element&#xff0c;iView&#xff0c;但是有时候我们又想去修改Ul框架的样式&#xff0c;当我们修改样式失败的时候&#xff0c;可以尝试一下/deep/&#xff0c;亲测有效。 那失败的原因是什么…

基于DBO-CNN-BiLSTM数据回归预测(多输入单输出),蜣螂优化算法优化CNN-BiLSTM-附代码

基于DBO-CNN-BiLSTM的数据回归预测是一种综合利用了深度学习中的多种技术的方法&#xff0c;包括卷积神经网络&#xff08;CNN&#xff09;、双向长短期记忆网络&#xff08;BiLSTM&#xff09;和注意力机制&#xff08;Attention&#xff09;。蜣螂优化算法用于优化CNN-BiLSTM…

揭秘’在家答答题,无需经验、无论男女、单号轻松日产200+的一个玩法

项目简介 公众号&#xff1a;老A程序站 这个项目是人人可参与的&#xff0c;无需支付任何费用&#xff0c;只需投入时间即可。每天的任务主要是回答问题。 项目 地 址 &#xff1a; laoa1.cn/1457.html 如果遇到不会的问题&#xff0c;可以直接使用百度进行搜索。我们通过…

【明道云】如何让用户可以新增但不能修改记录

【背景】 遇到一个需求场景&#xff0c;用户希望新增数据后锁住数据不让更改。 【分析】 在设计表单时直接将字段设置只读是不行的。字段设置只读将会直接让界面上此字段的前端组件不可编辑。包括新增时也无法填入。显然是不符合需求的。 需要既能新增&#xff0c;新增后又不…

5.6 物联网RK3399项目开发实录-Android开发之U-Boot 编译及使用(wulianjishu666)

物联网入门到项目实干案例下载&#xff1a; https://pan.baidu.com/s/1fHRxXBqRKTPvXKFOQsP80Q?pwdh5ug --------------------------------------------------------------------------------------------------------------------------------- U-Boot 使用 前言 RK U-B…

在香港服务器搭网站速度怎么样?

在香港服务器搭网站速度怎么样&#xff1f;一般要看用户所在地理位置&#xff0c;如果用户距离香港服务器较远&#xff0c;网络延迟会增加&#xff0c;导致加载速度变慢。 面对海外地区用户&#xff0c;香港作为亚洲连接海外的网络中转枢纽&#xff0c;多条国际海底电缆&#…

编译原理知识点整理

第一章 绪论 计算机语言发展历程 第一代语言&#xff1a;机器语言第二代语言&#xff1a;汇编语言第三代语言&#xff1a;高级语言(如C&#xff0c;C&#xff0c;Java等)第四代语言&#xff1a;极高级领域语言(如SQL)第五代语言&#xff1a;可视化配置语言第六代语言&#xff…

抢先看!界面控件DevExpress WPF 2024产品路线图预览(一)

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 本文将介绍2024年Dev…

事件穿透效果

讲述一下事件穿透的需求&#xff0c;大家可以根据实际情况来考虑是否采用这种方式来制作页面&#xff0c;&#xff08;项目中遇到了底部是地图&#xff0c;两侧面板&#xff0c;但是UI在设计的时候为了好看&#xff0c;会有很大的遮罩阴影部分&#xff0c;如果按照时间制作会导…

图书推荐|Bootstrap 5从入门到精通:视频教学版

示例源码、PPT课件、同步教学视频、作者微信答疑、教学大纲、其他丰富的教学资源 本书内容 《Bootstrap 5从入门到精通&#xff1a;视频教学版》结合示例和综合项目的演练&#xff0c;详细讲解Bootstrap开发技术&#xff0c;使读者快速掌握Bootstrap开发技能&#xff0c;提高使…

[SpringCloud] Feign Client 的创建 (二) (五)

文章目录 1.自动配置FeignAutoConfiguration2.生成 Feign Client2.1 从Feign Client子容器获取组件2.2 Feign Client子容器的创建2.3 构建Feign Client实例 1.自动配置FeignAutoConfiguration spring-cloud-starter-openfeign 包含了 spring-cloud-openfeign-core FeignAutoCo…

CSS实现小车旅行动画实现

小车旅行动画实现 效果展示 CSS 知识点 灵活使用 background 属性下的 repeating-linear-gradient 实现路面效果灵活运用 animation 属性与 transform 实现小车和其他元素的动画效果 动画场景分析 从效果图可以看出需要实现此动画的话&#xff0c;需要position属性控制元素…

机器学习(三)

神经网络: 神经网络是由具有适应性的简单单元组成的广泛并行互连的网络&#xff0c;它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。 f为激活(响应)函数: 理想激活函数是阶跃函数&#xff0c;0表示抑制神经元而1表示激活神经元。 多层前馈网络结构: BP(误差逆…

微服务demo(二)nacos服务注册与集中配置

环境&#xff1a;nacos1.3.0 一、服务注册 1、pom&#xff1a; 移步spring官网https://spring.io&#xff0c;查看集成Nacos所需依赖 找到对应版本点击进入查看集成说明 然后再里面找到集成配置样例&#xff0c;这里只截一张&#xff0c;其他集成内容继续向下找 我的&#x…

【Python】python+requests+excel+unittest+ddt实现接口自动化实例

目录 测试需求实现思路框架代码实例1. 环境准备和配置文件2. Excel接口数据及测试结果3. API封装4. 读取Excel数据5. 测试用例6. 日志和配置文件处理7. HTMLTestRunner生成可视化的html报告8. 报告通过飞书/邮件发送报告通过飞书发送报告通过邮件发送9. 入口函数10. 飞书Webhoo…