【Linux】线程同步和死锁

news2024/11/26 6:23:02

目录

死锁

什么是死锁

构成死锁的四个必要条件

如何避免死锁

线程同步

同步的引入

同步的方式

条件变量

条件变量的使用

整体代码


 

死锁

什么是死锁

        死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放 的资源而处于的一种永久等待状态.

        例如进程两个锁mtx1和mtx2,进程A执行某一段代码需要先申请mtx1,再申请mtx2;而进程B执行对应的代码需要现申请mtx2,再申请mtx1.

        某个时刻,进程A和B同时运行,进程A拿到了mtx1,进程B拿到了mtx2,但紧接着,进程A需要mtx2,但此时这把锁被进程B所占用,无法申请,便阻塞等待进程B的完成。而进程B需要mtx1,但是此时被A占用,需要等待进程A的完成,也阻塞在了这里。于是便造成了死锁。

        

构成死锁的四个必要条件

  • 互斥条件: 一个资源每次只能被一个执行流使用,其他进程无法同时访问该资源。
  • 请求与保持条件:即进程在请求资源时可以保持已占有的资源,即不释放自己原本的资源
  • 不剥夺条件:已经获得资源的进程不能被强行剥夺其他进程所拥有的资源,只有自愿释放
  • 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系。如A->B,B->A.

如何避免死锁

     我们知道了构成死锁的那个四个必要条件,只要破坏其中任意一个 条件即可:

  • 破坏互斥条件:尽量避免使用互斥资源,或者采用不同的资源访问方式,如读写锁,允许多个进程或线程同时访问某些资源。
  • 破坏请求与保持条件:如果申请多个锁失败,则释放自己已经拥有的资源
  • 破坏不剥夺条件:引入资源抢占机制,即允许操作系统对进程已获得的资源进行抢占。当其他进程紧迫需要某个资源时,系统可以终止或暂停某个进程,将其持有的资源释放分配给需要的进程。
  • 打破环路等待条件:采用全局资源排序策略,为每个资源指定一个唯一的编号,然后要求进程只能按照编号递增的顺序请求资源,这样可以避免环路等待的发生。

   以上所说的"资源"都也可以理解为锁。

线程同步

同步的引入

        上一章我们说的        

        1.多线程然后抢票的例子,我们发现虽然有多个线程,但是每一次基本上都是那一个线程在抢(比如优先级可能更高),其它线程抢不到,这就是一个线程频繁地申请到资源,造成别的线程饥饿问题。

        2.假设一个资源暂时没有了,而线程依旧在竞争锁,然后访问资源,访问不到然后释放锁没就这样一直进行,但此时也没有资源可用。这样就太过于浪费了。

        以上这些操作都是正确的,但是是不合理的!

        所以为了解决上面这系列问题,便引入了同步:主要是为了解决 访问临界资源合理性问题的.

按照一定的顺序,进行临界资源的访问

        1.对于问题一我们可以这样:当一个线程申请到资源后,使用完之后,排到其它线程后面,让其他线程先访问,如此进行下去。

        2.对于问题二,我们可以暂时每个线程发个号,当有资源时,再按照号的顺序来访问资源,而不是互相不正当竞争这份资源

所以线程同步的是:线程同步是指在并发编程中,通过协调多个线程的执行顺序以及对共享资源的访问来保证线程之间的正确交互

同步的方式

条件变量

        当我们申请临界资源时 ---> 要对临界资源是否存在做检测 ---> 检测的本质:也是访问临界资源 --->结论:对临界资源的检测,也一定是在加锁和解锁之间的。

        既然这样,那检测依然需要频繁地申请和释放锁,那么有没有办法让线程检测到资源不就绪的时候:

        a.不要让线程自己再频繁检测了,而是等待

        b.当条件就绪的时候,通知对应的线程,让它来进行资源的申请与访问。

为了满足上面的说话,这里就有引入条件变量。

        条件变量(Condition Variable)是一种同步原语,常用于多线程编程中进行线程间的等待和通知。它用于实现线程之间的同步和协作,使得一个线程可以等待某个条件的满足并被其他线程通知唤醒。

条件变量的使用

        使用条件变量需要配合互斥锁(pthread_mutex_t)来保证线程的安全操作。一般的使用步骤如下:

        首先我们要先创建一个条件变量,数据类型为 pthread_cond_t,同时也要创建互斥锁。

        

    pthread_mutex_t mtx;
    pthread_cond_t cond;

       初始化条件变量和互斥锁:互斥锁我们说了初始化方式了,这里说初始化条件变量的函数:

 pthread_cond_init,该函数原型如下:

       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);

        第一个参数为条件变量,第二个参数为条件变量属性,一般设为NULL。

        如果创建的条件变量是全局的,那么可以用下面的方法进行初始化:

       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

所以初始化代码如下: 

    pthread_cond_init(&cond,nullptr);//初始化条件变量
    pthread_mutex_init(&mtx,nullptr);//初始化锁

紧接着,我们创建4个线程,然后再创建一个结构体,里面包含了线程名,该线程调用的方法,条件变量和互斥锁,然后编写一个构造函数来初始化这些:

typedef void(*func_t)(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond);
class ThreadData
{
public:
    ThreadData(const string& name,func_t func,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
    :_name(name),_func(func),_pmtx(pmtx),_pcond(pcond)
    {}
public:
    string _name;//线程名
    func_t _func;//该线程对应的回调方法
    pthread_mutex_t* _pmtx;//互斥锁
    pthread_cond_t* _pcond;//条件变量
};

        然后开始编写每个线程的回调方法,这里其实不能很好地展示条件变量中锁的作用,我们需要在下一章生产者与消费者模型时,才能好好看出作用.

        这里每个线程方法,我们先利用pthread_cond_wait进行阻塞等待,当资源准备就绪的时候,才会继续向后执行。然后后面输出一条语句,

        该函数原型如下:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

        函数pthread_cond_wait用于使线程进入等待状态,并且会原子性地释放由mutex指定的互斥锁,进入等待状态后会阻塞等待,直到其他线程使用相同的条件变量调用pthread_cond_signal或pthread_cond_broadcast时,被唤醒并重新获得互斥锁。调用前需要确保已经加锁。返回时会重新获得互斥锁。

        其中第一个参数为条件变量,第二个参数为互斥锁。具体什么作用,下节课会讲。

        这是线程1的回调方法,线程2,3,4都是同样地,只不过名字不同。

void func1(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{
    while(true)
    {
        pthread_mutex_lock(pmtx);
        //if(临界资源不就绪) wait之前一般会进行检测,这里由于无法很好的模拟场景,就暂时不加if
        pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞
        cout << name << "running ..." << endl;
        pthread_mutex_unlock(pmtx);
    }
}

        现在创建好线程后,每个线程都被阻塞在了pthread_mutex_wait接口这里,所以我们需要再主函数中唤醒这些线程,共有两种方式:

pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);

参数为条件变量,至于唤醒哪一个线程,这是由调度器决定的,但顺序一定是固定的,当我们运行起来程序后:

        这样便保证了线程同步。 

这是一个一个线程的唤醒,如果我们想唤醒所有线程,这就需要用到pthread_cond_broadcast,

该函数原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

参数同样也为条件变量,函数pthread_cond_broadcast用于广播条件变量的信号,即唤醒所有等待此条件变量的线程。

 这样便一次能唤醒所有线程继续执行了。


一切完成之后,我们需要在最后销毁释放条件变量和互斥锁,函数为pthread_cond_destroy,

函数原型为:

       int pthread_cond_destroy(pthread_cond_t *cond);

参数同样为定义的条件变量,传进去之后,即可释放条件变量。


整体代码

        以上便是条件变量的一个大致使用流程,具体的理解下一章生产者消费者模型会讲解,这列理解了条件变量的用法即可。

        可以拷贝到自己平台下运行,编译时记得加上-lpthread,如下:

g++ -o mythread mythread.cc -lpthread

代码:

#include<iostream>
#include<string>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

using namespace std;

#define PTHREAD_NUM 4

typedef void(*func_t)(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond);
class ThreadData
{
public:
    ThreadData(const string& name,func_t func,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
    :_name(name),_func(func),_pmtx(pmtx),_pcond(pcond)
    {}
public:
    string _name;
    func_t _func;
    pthread_mutex_t* _pmtx;
    pthread_cond_t* _pcond;
};

void func1(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{
    while(true)
    {
        pthread_mutex_lock(pmtx);
        //if(临界资源不就绪) wait之前一般会进行检测,这里由于无法很好的模拟场景,就暂时不加if
        pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞
        cout << name << "running ..." << endl;
        pthread_mutex_unlock(pmtx);
    }
}
void func2(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{
    while(true)
    {
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞
        cout << name << "running ..." << endl;
        pthread_mutex_unlock(pmtx);
    }
}
void func3(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{
    while(true)
    {
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞
        cout << name << "running ..." << endl;
        pthread_mutex_unlock(pmtx);
    }
}
void func4(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{
    while(true)
    {
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞
        cout << name << "running ..." << endl;
        pthread_mutex_unlock(pmtx);
    }
}


void* Entry(void* args)
{
    ThreadData* td = (ThreadData*)args;//td在每一个线程自己私有的栈空间保存
    td->_func(td->_name,td->_pmtx,td->_pcond);
    delete td;

    return nullptr;
}
int main()
{
    pthread_mutex_t mtx;
    pthread_cond_t cond;

    pthread_mutex_init(&mtx,nullptr);
    pthread_cond_init(&cond,nullptr);


    pthread_t tids[PTHREAD_NUM];
    //定义四个线程的回调方法
    func_t funcs[PTHREAD_NUM] = {func1,func2,func3,func4};
    for(int i = 0; i < PTHREAD_NUM; i++)
    {
        string name = "thread ";
        name += to_string(i+1);
        ThreadData* td = new ThreadData(name,funcs[i],&mtx,&cond);
        pthread_create(tids+i,nullptr,Entry,(void*)td);
    }

    //这里为了方便演示pthread_cond_wait,在没有用signal或broadcast唤醒前,一直处于阻塞状态
    sleep(5);
    //控制线程
    while(true)
    {
        cout << "wake up thread run code ..." << endl;
        //pthread_cond_signal(&cond);//唤醒一个线程
        pthread_cond_broadcast(&cond);//唤醒全部线程
        sleep(1);

    }

    for(int i = 0; i < PTHREAD_NUM; i++)
    {
        pthread_join(tids[i],nullptr);
    }

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【EI复现】一种建筑集成光储系统规划运行综合优化方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

大模型如何可信?字节跳动研究的最新《可信赖的大型语言模型》综述,提出评估 LLMs 可信度时需要考虑的七大维度

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 论文地址&#xff1a;Trustworthy LLMs: a Survey and Guideline for Evaluating Large Language Models’ Alignment 在将大型语言模型&#xff08;…

Markdown编辑器的使用

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

电路综合原理与实践---T衰减与PI衰减的详细计算理论与设计仿真

电路综合原理与实践—T衰减与PI衰减的详细计算理论与设计仿真 最近要找工作在刷笔试题目&#xff0c;会刷到关于T衰减的理论计算问题&#xff0c;一直搞不明白怎么算的&#xff0c;搞明白之后给大家伙来分享一下。 基础理论可以参考&#xff1a;电阻衰减网络计算&#xff08;P…

点燃性能火箭!揭秘内联函数的魔法 ✨

目录 前言&#xff1a;探索函数调用的微观世界 —— 从调用到跳转 &#x1f680; 函数调用的微观世界 &#x1f31f; 深入理解栈、堆以及堆栈帧&#x1f511; 栈&#xff08;Stack&#xff09;&#xff1a; 堆&#xff08;Heap&#xff09;&#xff1a; 堆栈帧&#xff08…

4.0 Spring Boot入门

1. Spring Boot概述 Spring Boot介绍 Spring Boot是Pivotal团队在2014年推出的全新框架&#xff0c;主要用于简化Spring项目的开发过程&#xff0c;可以使用最少的配置快速创建Spring项目。 Spring Boot版本 2014年4月v1.0.0.RELEASE发布。 ​ 2.Spring Boot特性 约定优于配…

LED为何通过电流控制?

前段时间&#xff0c;散热部的同事咨询我关于手机的闪光灯输出电压值&#xff0c;说实话&#xff0c;一时间把我问住了。关于闪光灯&#xff0c;以往我们关注电流值&#xff0c;电压值很少关注。虽说手机的闪光灯驱动IC输出为BOOST电路&#xff0c;但是输出电压到多少&#xff…

SolidUI社区-元数据文档

背景 随着文本生成图像的语言模型兴起&#xff0c;SolidUI想帮人们快速构建可视化工具&#xff0c;可视化内容包括2D,3D,3D场景&#xff0c;从而快速构三维数据演示场景。SolidUI 是一个创新的项目&#xff0c;旨在将自然语言处理&#xff08;NLP&#xff09;与计算机图形学相…

20-最难的问题

题目 NowCoder生活在充满危险和阴谋的年代。为了生存&#xff0c;他首次发明了密码&#xff0c;用于军队的消息传递。假设你是军团中的一名军官&#xff0c;需要把发送来的消息破译出来、并提供给你的将军。 消息加密的办法是&#xff1a;对消息原文中的每个字母&#xff0c;分…

【AI】p54-p58导航网络、蓝图和AI树实现AI随机移动和跟随移动、靠近玩家挥拳、AI跟随样条线移动思路

p54-p58导航网络、蓝图和AI树实现AI随机移动和跟随移动、靠近玩家挥拳、AI跟随样条线移动思路 p54导航网格p55蓝图实现AI随机移动和跟随移动AI Move To&#xff08;AI进行移动&#xff09;Get Random Pointln Navigable Radius&#xff08;获取可导航半径内的随机点&#xff09…

爬虫练手项目——获取龙族小说全文

网站信息 目标网站信息如下&#xff1a;包含了龙族1-5全部内容 代码 import requests from bs4 import BeautifulSoup import os import timeheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Sa…

全球劳动力革命,Papaya Global 打破薪资界限

员工需求和劳动力结构的进一步变化&#xff0c;只会增加对更加自动化和全面的全球薪资解决方案的需求。 远程工作潮流与全球劳动力的蓬勃发展&#xff0c;使得企业在全球范围内&#xff0c;寻找最优秀的人才成为可能。然而&#xff0c;随之而来的复杂薪资管理挑战&#xff0c;也…

C++ QT(二)

目录 Qt 控件按钮QPushButton控件简介用法示例运行效果 QToolButton控件简介用法示例运行效果 QRadioButton控件简介用法示例运行效果 QCheckBox控件简介用法示例运行效果 QCommandLinkButton控件简介用法示例运行效果 QDialogButtonBox控件简介用法示例运行效果 输入窗口部件Q…

HCIP的BGP小综合实验

一、实验要求 1.R2-7每台路由器均存在一个环回接口用于建立邻居&#xff1b; 同时还存在一个环回来代表连接用户的接口&#xff1b; 最终这些连接用户的接口网络需要可以和R1/8的环回通讯。 2.AS2网段地址172.16.0.0/16&#xff0c;减少路由条目。 二、实验过程 2.1 配置IP以…

【JVM】CPU飙高排查方案与思路

文章目录 CPU飙高排查方案与思路 CPU飙高排查方案与思路 1.使用top命令查看占用cpu的情况 2.通过top命令查看后&#xff0c;可以查看是哪一个进程占用cpu较高&#xff0c;上图所示的进程为&#xff1a;40940 3.查看进程中的线程信息 4.可以根据进程 id 找到有问题的线程&a…

【C++11智能指针】

c智能指针 手动管理内存很容易造成内存泄漏&#xff0c;现代c的智能指针可以在很大程度上帮我们缓解这个问题&#xff0c;降低我们的手动管理内存的心智负担&#xff0c;智能指针有好几种&#xff0c;比如shared_ptr、unique_ptr还有weak_ptr 共享指针shared_ptr 共享指针会…

【TX 企业微信私有化历史版本 API 信息泄露】

目录 影响版本 复现过程 修复方式 影响版本 影响私有化部署&#xff1a; toB toG版微信 2.5.x 版本 2.6.930000 版本以下 危险程度&#xff1a;高危。攻击者可以进行获取企业的部门信息&#xff0c;员工信息&#xff0c;如权限较高包括应用获取&#xff0c;记录文件等等均…

c语言操作符

目录 运算符 移位操作符 左移操作符 右移操作符 位操作符 按位与& 按位或| 按位异或^ 异或交换数字 计算二进制中1的个数 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用、函数调用和结构成员 隐式类型转换 整形提升实例&#xff1a; 算术转换 操作…

2023企业微信0day漏洞复现以及处理意见

2023企业微信0day漏洞复现以及处理意见 一、 漏洞概述二、 影响版本三、 漏洞复现小龙POC检测脚本: 四、 整改意见 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#x…

【从零开始学Kaggle竞赛】泰坦尼克之灾

目录 0.准备1.问题分析挑战流程数据集介绍结果提交 2.代码实现2.1 加载数据2.1.1 加载训练数据2.1.2 加载测试数据 2.2 数据分析2.3 模型建立与预测 3.结果提交 0.准备 注册kaggle账号后&#xff0c;进入titanic竞赛界面 https://www.kaggle.com/competitions/titanic 进入后界…