【Linux】线程——线程互斥的概念、锁的概念、互斥锁的使用、死锁、可重入和线程安全、线程同步、条件变量的概念和使用

文章目录

  • Linux线程
    • 4. 线程互斥
      • 4.1 线程互斥的概念
      • 4.2 锁的概念
        • 4.2.1 互斥锁的概念
        • 4.2.2 互斥锁的使用
        • 4.2.3 死锁
        • 4.2.4 可重入和线程安全
    • 5. 线程同步
      • 5.1 条件变量的概念
      • 5.2 条件变量的使用

Linux线程

4. 线程互斥

  我们之前使用了线程函数实现了多线程的简单计算模拟器。

  可以看到多线程可以很好的运行并且计算得到我们想要的结果。

在这里插入图片描述
  

  那我们照猫画虎一样,看看可不可以实现多线程模拟抢票的过程:

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <unistd.h>

#define NUM 5 //线程数量
int tickets=100; //全局变量作为剩余的票数

//线程执行的模拟抢票函数
void *getTickets(void *args)
{
    int* id=static_cast<int*>(args);
    while(true)
    {
        if(tickets>0)
        {
            std::cout<<"thread id: "<<*id<<" remaining tickets: "<<tickets--<<std::endl;
        }
        else 
        {
            break;
        }
    }
    return nullptr; 
}

int main()
{
    //使用多线程模拟抢票过程
    std::vector<pthread_t> threads;
    for(int i=1;i<=5;i++)
    {
        pthread_t tid;
        pthread_create(&tid,nullptr,&getTickets,(void*)(&i));
        threads.push_back(tid);
    }
    //销毁我们创建的线程资源
    for(auto e:threads)
    {
        pthread_join(e,nullptr);
    }
    return 0;
}

在这里插入图片描述
  

  我们发现问题了,剩余的票数竟然出现了负数,在现实中,抢到 -1 张票显然是不实现的事情。多个线程同时访问和修改临界资源(票的数量)会出现数据不一致问题。

  这里就可以引出和线程互斥相关的概念了:

  临界资源:多线程执行流共享的资源就叫做临界资源。

  临界区:每个线程内部,访问临界资源的代码,就叫做临界区。

  互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。

  原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

  

4.1 线程互斥的概念

  概念:线程互斥指的是在同一时刻,只允许一个线程访问特定的资源或执行特定的代码段,以避免多个线程同时操作导致的数据不一致、资源竞争等问题。

  目的:确保线程在访问共享资源时的正确性和一致性。如果多个线程同时对共享资源进行读写操作,可能会出现不可预测的结果,例如数据被破坏、计算错误等。

  实现方式:通常通过互斥锁(Mutex)、信号量(Semaphore)等机制来实现线程互斥。 以互斥锁为例,当一个线程获取到互斥锁后,其他试图获取该锁的线程会被阻塞,直到持有锁的线程释放锁。

  

  大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

  但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

  多个线程并发的操作共享变量,会带来一些问题。

  

   回到上面的抢票代码出错的原因,是由于多线程同时并发操作共享资源,导致的数据错误,怎么解决这个问题?我们要引出锁这个概念了。

  

4.2 锁的概念

  锁是用于控制对共享资源访问的机制,以确保多线程或多进程环境下数据的一致性和正确性。

  锁的主要作用是防止多个线程或进程同时对共享资源进行读写操作,从而避免数据竞争、不一致性和错误的结果。

  Linux 中的锁可以分为以下几种类型:

  互斥锁(Mutex):确保在同一时刻只有一个线程或进程能够访问被保护的资源。

  读写锁(Read-Write Lock):分为读锁和写锁。允许多个读线程同时获取读锁来读取资源,但在获取写锁进行写入时,会阻塞其他的读锁和写锁请求。

  自旋锁(Spin Lock):当一个线程试图获取自旋锁而该锁已被占用时,线程会一直循环检测锁是否被释放,而不是进入阻塞状态。适用于锁被持有的时间较短的情况,避免了线程切换的开销。

  

  我们在下面使用互斥锁解决我们的问题。

  

4.2.1 互斥锁的概念

  互斥锁(Mutex)属于锁的一种类型。

  概念:互斥锁用于保护共享资源,确保在同一时刻只有一个线程能够访问被其保护的临界区。

  工作原理:当一个线程想要访问受互斥量保护的资源时,它首先需要获取互斥量。如果此时互斥量未被其他线程持有,该线程成功获取并可以进入临界区进行操作。如果互斥量已被其他线程持有,那么当前线程将被阻塞,直到持有互斥量的线程释放它。

  优点:提供了简单而有效的方式来避免多线程对共享资源的并发访问冲突。确保了共享资源在多线程环境下的一致性和正确性。

  缺点:可能导致线程阻塞和上下文切换,从而影响性能。如果使用不当,可能会引起死锁等问题。

在这里插入图片描述

  

  注意:锁是一个广义的概念,互斥量是一种特定的锁,它的主要在同一时刻只允许一个线程拥有访问权。

  

4.2.2 互斥锁的使用

初始化互斥量

  初始化互斥量有两种方法:

  方法1,静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

  方法2,动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

  参数:mutex:要初始化的互斥量    attr:NULL

在这里插入图片描述

  

销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex)

  销毁互斥量需要注意:

  使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

  不要销毁一个已经加锁的互斥量

  已经销毁的互斥量,要确保后面不会有线程再尝试加锁

  

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

  返回值:成功返回0,失败返回错误号

  调用 pthread_ lock 时,可能会遇到以下情况:

  互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功

  发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁

在这里插入图片描述

  

  这样我们就可以解决负票的情况了。

在这里插入图片描述

  

4.2.3 死锁

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

  死锁四个必要条件

  (1)互斥条件: 一个资源每次只能被一个执行流使用

  (2)请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

  (3)不剥夺条件: 一个执行流已获得的资源,在末使用完之前,不能强行剥夺

  (4)循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系

  

  避免死锁

  (1)破坏死锁的四个必要条件

  (2)加锁顺序一致

  (3)避免锁未释放的场景

  (4)资源一次性分配

  

  避免死锁算法

  死锁检测算法:

  死锁检测算法通过分析资源分配情况来判断系统是否处于死锁状态。常见的基于资源分配图,若图中存在资源请求的循环等待,则判定为死锁。
  例如,进程 P1 等待 P2 占用的资源,P2 等待 P3 占用的资源,P3 又等待 P1 占用的资源,形成循环等待,即死锁。

  银行家算法:

  银行家算法模拟银行资金分配,用于决定资源分配是否安全,以避免死锁。
系统有多种资源和多个进程,算法记录每个进程已分配和还需的资源量。若为某进程分配资源后,系统仍能保证所有进程可完成并释放资源,就进行分配,否则拒绝。
  比如,资源有限,进程 P1 申请资源,算法判断分配给 P1 后,其他进程能否顺利完成,能则分配,不能则拒绝。

  

4.2.4 可重入和线程安全

  可重入和线程安全概念:

  重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

  线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

  

  可重入与线程安全联系:

  (1)函数是可重入的,那就是线程安全的

  (2)函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

  (3)如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

  

5. 线程同步

  线程同步是指多个线程在协同工作时,通过特定的机制来协调它们的执行顺序和对共享资源的访问,以确保线程之间能够正确、有序地协作,避免出现数据不一致、竞态条件等问题。

  在多线程环境中,由于线程的执行是并发的,如果不对线程的操作进行同步控制,可能会导致以下情况:

  数据竞争:多个线程同时读写同一个共享数据,导致结果不可预测。

  不一致的状态:线程对共享资源的部分修改可能会被其他线程打断,导致资源处于不一致的状态。

  线程同步的常见方法包括使用互斥量、信号量、条件变量等。

  

5.1 条件变量的概念

  当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

  例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

  

同步概念与竞态条件:

  同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

  竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

  

5.2 条件变量的使用

  条件变量函数:

初始化

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

  参数:

  cond:要初始化的条件变量   attr:NULL

  

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

  

等待条件满足

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

  参数:

  cond:要在这个条件变量上等待

  mutex:互斥量,后面详细解释

  

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

  

  利用条件变量实现简易计数器:

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

int cnt=0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

struct threadData
{
public:
    threadData(int id)
        :_id(id)
    {}

    int _id;
};

void *handle(void *args)
{
    //分离线程,程序结束自动销毁线程
    pthread_detach(pthread_self());
    //threadData* td=static_cast<threadData*>(args);
    uint64_t id=(uint64_t)args;

    while(true)
    {
        pthread_mutex_lock(&mutex); 
        if(cnt<100)
        {
            pthread_cond_wait(&cond, &mutex); //等待条件变量
            std::cout<<"thread id : "<<id<<" , cnt = "<<cnt++<<std::endl;
            pthread_mutex_unlock(&mutex);
        }
        else 
        {
            pthread_mutex_unlock(&mutex);
            break;
        }     
        usleep(1000);   
    }

    return nullptr;
}

int main()
{
    //创建五个线程并且执行函数
    for(uint64_t i=1;i<=5;i++)
    {
        pthread_t tid;
        //threadData td(i);
        pthread_create(&tid,nullptr,handle,(void*)i);
        usleep(1000);
    }
    
    usleep(1000);

    while(1)
    {
        //pthread_cond_signal(&cond); //唤醒等待队列中的第一个线程
        pthread_cond_broadcast(&cond);
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

  

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

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

相关文章

python 循环优化

python循环是非常耗时的&#xff0c;所以遇到比较大的数组就要对循环做优化&#xff0c;代码&#xff1a; for i in range(pred_density_up.shape[0]):for j in range(pred_density_up.shape[1]):if pred_density_up[i][j] > 0.1:points.append([j , i ])上面的循环是最长见…

【 香橙派 AIpro评测】烧系统运行部署LLMS大模型跑开源yolov5物体检测并体验Jupyter Lab AI 应用样例(新手入门)

文章目录 一、引言⭐1.1下载镜像烧系统⭐1.2开发板初始化系统配置远程登陆&#x1f496; 远程ssh&#x1f496;查看ubuntu桌面&#x1f496; 远程向日葵 二、部署LLMS大模型&yolov5物体检测⭐2.1 快速启动LLMS大模型&#x1f496;拉取代码&#x1f496;下载mode数据&#x…

nginx代理缓存

在服务器架构中&#xff0c;反向代理服务器除了能够起到反向代理的作用之外&#xff0c;还可以缓存一些资源&#xff0c;加速客户端访问&#xff0c;nginx的ngx_http_proxy_module模块不仅包含了反向代理的功能还包含了缓存功能。 1、定义代理缓存规则 参数详解&#xff1a; p…

esplice老项目(非maven)导入idea问题

解决导入idea显示不正常 老项目导入idea后&#xff0c;显示为如下所示&#xff1a; 显示的不太正常&#xff0c;正常显示为下面这个样子&#xff1a; 解决 非老项目 idea的项目中所有的文件全部变成了.java(已解决) 老项目 以下内容参考&#xff1a;idea导入项目后java文…

Word创建多级列表的样式

Word创建多级列表的样式 要求结果方法创建样式修改样式设置段落创建快捷键 关联多级列表 要求 创建自定义的三级列表样式&#xff0c;要求标题均为黑体&#xff0c;小四字号&#xff0c;1.5倍行距&#xff0c;有快捷键。 结果 方法 在样式中创建三个样式。 创建样式 录入名…

【入门级】docker

开头处生动的描述一下”码头工人”吧&#xff1a;小鲸鱼&#xff08;登记处Registry&#xff1a;比如docker hub官方&#xff09;背着好多集装箱&#xff08;仓库repository&#xff1a;存放各种各样的镜像&#xff0c;一般存放的是一类镜像&#xff0c;这一类镜像中通过tag 版…

kubernetes k8s Deployment 控制器配置管理 k8s 红蓝部署 金丝雀发布

目录 1、Deployment控制器&#xff1a;概念、原理解读 1.1 Deployment概述 1.2 Deployment工作原理&#xff1a;如何管理rs和Pod&#xff1f; 2、Deployment资源清单文件编写技巧 3、Deployment使用案例&#xff1a;创建一个web站点 4、Deployment管理pod&#xff1a;扩…

邮箱验证码功能开发

该文章用于记录怎么进行邮箱验证码开发。 总所周知&#xff0c;我们在某些网站进行注册的适合总是会遇到什么填写邮箱&#xff0c;邮箱接收验证码&#xff0c;验证通过后才可以继续注册&#xff0c;那么这个功能是怎么实现的呢&#xff1f; 一&#xff0c;准备工作 1.1 邮箱…

贪心算法(2024/7/16)

1合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;inter…

朴素模式匹配算法与KMP算法(非重点)

目录 一. 朴素模式匹配算法1.1 什么是字符串的匹配模式1.2 朴素模式匹配算法1.3 通过数组下标实现朴素模式匹配算法 二. KMP算法2.1 算法分析2.2 用代码实现&#xff08;只会出现在选择题&#xff0c;考察代码的概率不大&#xff09; 三. 手算next数组四. KMP算法的进一步优化4…

3D可视化赋能智慧园区安防管理,开启园区管理新篇章!

3D可视化&#xff0c;主要是研究大规模非数值型信息资源的视觉呈现&#xff0c;以及利用图形方面的技术与方法&#xff0c;帮助人们理解和分析数据。 传统园区的信息化往往数据不互通&#xff0c;业务难融合&#xff0c;长期面临着服务体验差、综合安防弱、运营效率低、管理成本…

MySQL执行状态查看与分析

当mysql出现性能问题时&#xff0c;一般会查看mysql的执行状态&#xff0c;执行命令&#xff1a; show processlist 各列的含义 列名含义id一个标识&#xff0c;你要kill一个语句的时候使用&#xff0c;例如 mysql> kill 207user显示当前用户&#xff0c;如果不是root&…

烟雾监测与太阳能源:实验装置在其中的作用

太阳光在烟雾中的散射效应研究实验装置是一款模拟阳光透过烟雾环境的设备。此装置能帮助探究阳光在烟雾中的传播特性、散射特性及其对阳光的影响。 该装置主要包括光源单元、烟雾发生装置、光学组件、以及系统。光源单元负责产生类似于太阳光的光线&#xff0c;通常选用高亮度的…

2024牛客暑期多校训练营1 A题(A Bit Common )解题思路

前言&#xff1a; 今年和队友报了牛客暑期多校比赛&#xff0c;写了一下午结果除了签到题之外只写出了一道题&#xff08;A&#xff09;&#xff0c;签到题没什么好说的&#xff0c;其他题我也没什么好说的&#xff08;太菜了&#xff0c;根本写不出来&#xff09;&#xff0c;…

django-ckeditor富文本编辑器

一.安装django-ckeditor 1.安装 pip install django-ckeditor2.注册应用 INSTALLED_APPS [...ckeditor&#xff0c; ]3.配置model from ckeditor.fields import RichTextFieldcontent RichTextField()4.在项目中manage.py文件下重新执行迁移&#xff0c;生成迁移文件 py…

常见的数据分析用例 —— 信用卡交易欺诈检测

文章目录 引言数据集分析1. 读入数据并快速浏览2.计算欺诈交易占数据集中交易总数的百分比3. 类别不平衡对模型的影响3.1 总体思路&#xff08;1&#xff09;数据的划分&#xff08;2&#xff09;训练模型&#xff08;3&#xff09;测试模型&#xff08;4&#xff09;解决不平衡…

django报错(二):NotSupportedError:MySQL 8 or later is required (found 5.7.43)

执行python manage.py runserver命令时报版本不支持错误&#xff0c;显示“MySQL 8 or later is required (found 5.7.43)”。如图&#xff1a; 即要MySQL 8或更高版本。但是企业大所数用的还是mysql5.7相关版本。因为5.7之后的8.x版本是付费版本&#xff0c;贸然更新数据库肯定…

python自动化之用flask校验接口token(把token作为参数)

用到的库&#xff1a;flask 实现效果: 写一个接口&#xff0c;需要token正确才能登录 代码&#xff1a; # 导包 from flask import Flask,request,jsonify,json # 创建一个服务 appFlask(__name__) # post请求&#xff0c;路径&#xff1a;/query app.route(/query, met…

框架设计MVC

重点&#xff1a; 1.用户通过界面操作&#xff0c;传输到control&#xff0c;control可以直接去处理View&#xff0c;或者通过模型处理业务逻辑&#xff0c;然后将数据传输给view。 2.control包含了model和view成员。 链接&#xff1a; MVC框架详解_mvc架构-CSDN博客 MVC架…

【香橙派 Orange pi AIpro】| 搭建部署基于Yolov5的车牌识别系统

【香橙派 Orange pi AIpro】| 搭建部署基于Yolov5的车牌识别系统 一、香橙派 Orange pi AIpro 开发板介绍及实物开箱1.1 开发板介绍1.2 产品详情图1.3 开箱实物 二、开发部署预先准备2.1 镜像介绍与烧录2.2 启动开发板2.3 连接开发板 三、基于Yolov5的车牌识别系统3.1 项目介绍…