【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)
{uint64_t id=(uint64_t)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 <vector>
#include <string>
#include <cstring>
#include <unistd.h>#define NUM 5 //线程数量
int tickets=100; //全局变量作为剩余的票数pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //全局变量初始化锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量//线程执行的模拟抢票函数
void *getTickets(void *args)
{uint64_t id=(uint64_t)args;while(true){pthread_mutex_lock(&mutex); //申请锁成功,才能往后执行,不成功,阻塞等待if(tickets>0){pthread_cond_wait(&cond, &mutex); //等待条件变量if(tickets>0) //唤醒了条件变量还要对ticket进行检查{std::cout<<"thread id: "<<id<<" remaining tickets: "<<tickets--<<std::endl;}pthread_mutex_unlock(&mutex);}else {pthread_mutex_unlock(&mutex);break;}//usleep(10); //对于抢了票的线程进行一点等待,稍加限制}return nullptr; 
}int main()
{pthread_mutex_init(&mutex, nullptr);//使用多线程模拟抢票过程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);}usleep(10);while(1){//pthread_cond_signal(&cond); //唤醒等待队列中的第一个线程pthread_cond_broadcast(&cond);if(tickets<=0) break; //主线程只有一个执行流不需要加锁}return 0;
}

  

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

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

相关文章

如何在 Android 中删除和恢复照片

对于智能手机用户来说&#xff0c;相机几乎已经成为一种条件反射&#xff1a;你看到值得注意的东西&#xff0c;就拍下来&#xff0c;然后永远保留这段记忆。但如果那张照片不值得永远保留怎么办&#xff1f;众所周知&#xff0c;纸质快照拿在手里很难舍弃&#xff0c;而 Andro…

php通用防伪溯源查询系统可自定义字段

性能描述&#xff1a; 1. 适合中小规模查询&#xff0c;建议100万条以内(具体视服务器配置)。 2. 自定义前台是否使用验证码&#xff0c;即可以自行开启/关闭验证码。 3. 灵活支持N列内容&#xff0c;只要前几列按照规则;其余列均自定义内容。 4. 可单个新加&#xff0c;也…

【测开能力提升-fastapi框架】fastapi能力提升 - ORM增删改查操作

1 ORM操作 注释&#xff1a; fastapi缺少合适的ORM&#xff0c;官方推荐&#xff1a;sqlalchemy&#xff0c;但我们就不听官方的话&#xff0c;我们选择使用Tortoise ORM&#xff0c;因为他是支持异步的 1.1 tortoise ORM支持的数据库 PostgreSQL&#xff08;使用asyncpg&am…

上市公司产品市场竞争程度指数(1990-2023年)

数据来源&#xff1a;基础数据来源于上市公司年报以及证监会及统计局时间跨度&#xff1a; 1990-2023年数据范围&#xff1a;企业及行业层面数据指标&#xff1a;本数据包含赫芬达尔指数/行业集中度/勒纳指数三个数据: 行业代码 行业名称 统计截止日期 市场类型 是否…

指针!!C语言(第二篇)

目录 一. 数组名的理解 二. 一维数组传参的本质 三. 冒泡排序法 四. 二级指针与指针数组 五. 字符指针变量与数组指针 一. 数组名的理解 在我们对指针有了初步的理解之外&#xff0c;今天我们来掌握一些新的知识就是数组与指针&#xff0c;第一个对数组名的了解&#xff…

#三元运算符(python/java/c)

引入&#xff1a;什么是三元运算符呢&#xff1f;无疑其操作元有三个&#xff0c;一个是条件表达式&#xff0c;剩余两个为值&#xff0c;条件表达式为真时运算取第一个值&#xff0c;为假时取第二个值。 一 Python true_expression if condition else false_expressi…

matlab永磁同步电机反馈试验装置的设计和永磁同步电机仿真

1、内容简介 略 85-可以交流、咨询、答疑 2、内容说明 略 摘要&#xff1a;得益于电力电子器件及控制技术的高速发展&#xff0c;使得电机的应用越来越方便及精确&#xff0c;适应了实际应用对电机性能及质量提出的更高要求和标准。同时电机测试技术也因为电力电子技术的发…

AI多模态实战教程:面壁智能MiniCPM-V多模态大模型问答交互、llama.cpp模型量化和推理

一、项目简介 MiniCPM-V 系列是专为视觉-语⾔理解设计的多模态⼤型语⾔模型&#xff08;MLLMs&#xff09;&#xff0c;提供⾼质量的⽂本输出&#xff0c;已发布4个版本。 1.1 主要模型及特性 &#xff08;1&#xff09;MiniCPM-Llama3-V 2.5&#xff1a; 参数规模: 8B性能…

将Excel或CSV文件导入MySQL

数据库信息 版本:mysql-5.7.22 字符集如下 一、将 Excel 文件导入 MySQL,此时 MySQL 中不存在该表。 在数据库中,右键-导入向导

刚刚 威尼斯影评人周公布 2024 年电影阵容 包括敏感纪录片《本土》

《本土》 威尼斯影评人周是威尼斯电影节专门为首次拍摄电影的人设立的侧边活动&#xff0c;该活动公布了第 39 届威尼斯电影节的七部竞赛片和两部非竞赛片的入选名单&#xff0c;第 39 届威尼斯电影节将于 8 月 28 日至 9 月 7 日举行。 较为及时的作品之一是美国导演迈克尔普…

【IntelliJ IDEA】一篇文章集合所有IDEA的所有设置

IntelliJ IDEA 是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;其设置涵盖了多个方面&#xff0c;以满足不同开发者的需求和偏好。由于 IDEA 的设置相当复杂和详尽&#xff0c;这里无法在一篇简短的文章中详细介绍所有设置。然而&#xff0c;我可以提供一…

持续集成07--Jenkins配置Allure测试报告

前言 在持续集成&#xff08;CI&#xff09;流程中&#xff0c;自动化测试报告是评估软件质量和追踪问题的重要工具。Allure Framework作为一个轻量级且功能丰富的测试报告工具&#xff0c;能够生成详细的测试报告&#xff0c;帮助团队更好地理解测试结果。本章节“持续集成07-…

Java二十三种设计模式-装饰器模式(7/23)

装饰器模式&#xff1a;动态扩展功能的灵活之选 引言 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;用于在不修改对象自身的基础上&#xff0c;通过添加额外的职责来扩展对象的功能。 基础知识&#xff0c;java设计模式总体来说设计…

特征工程方法总结

方法有以下这些 首先看数据有没有重复值、缺失值情况 离散&#xff1a;独热 连续变量&#xff1a;离散化&#xff08;也成为分箱&#xff09; 作用&#xff1a;1.消除异常值影响 2.引入非线性因素&#xff0c;提升模型表现能力 3.缺点是会损失一些信息 怎么分&#xff1a;…

爬取百度图片,想爬谁就爬谁

前言 既然是做爬虫&#xff0c;那么肯定就会有一些小心思&#xff0c;比如去获取一些自己喜欢的资料等。 去百度图片去抓取图片吧 打开百度图片网站&#xff0c;点击搜索xxx&#xff0c;打开后&#xff0c;滚动滚动条&#xff0c;发现滚动条越来越小&#xff0c;说明图片加载…

3D 渲染一个房屋需要多长时间?

3D 渲染一个房屋总共需要 6-10 个工作日&#xff0c;主要取决于项目的复杂性和最终 3D 渲染的质量&#xff0c;图像越逼真&#xff0c;效果图渲染所需时间就越长。 1.3D建模 创建 3D 模型是第一步&#xff0c;所需时间可能因项目的复杂程度而有很大差异。一个简单的住宅渲染可…

D3.高精度

1.分类情况 AB、A-B、A*a、A/b A和B指的是超大超长整数&#xff0c;长度<1e6; a的值<10000&#xff1b; 2.大整数的存储 int 变量肯定是存不了这么大的数的&#xff0c;做法是将大整数先存到string字符串&#xff0c;再使用字符串的访问方式&#xff0c;将每一位数存到…

单机游戏分享:波与月夜之青莲单机游戏下载,2D和风动作游戏

在《波与月夜之青莲》中穿越一个充满神话和传奇的神秘世界。这是款丰富多彩的手绘冒险游戏&#xff0c;灵感来自于日本的民间传说。 扮演波&#xff0c;一朵从天而降的天体花&#xff0c;在一道古老而神秘的仪式中扮演关键的角色&#xff0c;展开一段神秘的旅程。使用你的传说…

一、C#概述

本文是网页版《C# 12.0 本质论》第一章解读。欲完整跟踪本系列文章&#xff0c;请关注并订阅我的Essential C# 12.0解读专栏。 前言 第一章的内容非常简单&#xff0c;毕竟仅仅是Introducing C#。不过正如《0.前言》所述&#xff0c;《C# 12.0本质论》本身就不是一本零基础的…

rv1126利用rkmedia、opencv、rockx……完成人脸识别

一、总体框架 视频采集、处理使用rkmedia&#xff1a;vi模块进行视频输入、rga模块进行视频处理 人脸识别&#xff1a;先获取rga输出码流&#xff0c;再调用rkmedia的模型对人脸进行推理&#xff08;线程1&#xff09; 打框框&#xff1a;opencv&#xff08;线程2&#xff0…