【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://xiahunao.cn/news/3245768.html

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

相关文章

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【26】【内网穿透】cpolar

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【27】【内网穿透】cpolar 内网穿透cpolar内网穿透联调配置练习—使用公网地址访问gulimall.com参考 内网穿透 正常的外网需要访问我们项目的流程是&#xff1a; 买服务器并且有公网固定…

【数据结构】深入理解Floyd最短路径算法:全面解析及Python实现

文章目录 一、Floyd-Warshall算法简介二、Floyd-Warshall算法的数学表述三、Floyd-Warshall算法的Python实现四、Floyd-Warshall算法的应用场景五、Floyd-Warshall算法的优缺点六、优化与改进七、总结 Floyd-Warshall算法是一种用于解决加权图中最短路径问题的经典算法。该算法…

基于Ubuntu2310搭建openstack高可用集群B版

openstack-ha 环境初始化安装haproxy安装keepalived数据库集群高可用rabbitmq集群高可用memcache集群配置 keystone高可用glance高可用placement高可用nova高可用neutron高可用horizon高可用 本实验使用两台节点master和node配置haproxy高可用&#xff0c;keepliaved配置主备抢…

极验设备指纹HarmonyOS 鸿蒙版SDK官方下载

近日&#xff0c;华为开发者大会&#xff08;HDC 2024&#xff09;在东莞召开。在大会开幕日的首场主题演讲中&#xff0c;华为宣布当前已有TOP5000应用成为鸿蒙原生应用&#xff0c;350&#xff0b;SDK已适配HarmonyOS NEXT版本。其中&#xff0c;极验作为其重要伙伴&#xff…

JWT令牌详细解析

JWT令牌 前言一、JWT是什么&#xff1f;二、JWT与传统CookieSession的对比三、JWT1. JWT的功能2. JWT的结构3. JWT的使用 前言 主要介绍了SpringBoot集成JWT令牌详细说明,JWT方式校验方式更加简单便捷化&#xff0c;无需通过redis缓存&#xff0c;而是直接根据token取出保存的…

解决C#读取US7ASCII字符集oracle数据库的中文乱码

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全栈领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、解决方法二、安装System.Data.OleDb连接库三…

隐藏需求缺失的4种解决技巧

在需求分析过程中&#xff0c;隐藏需求的缺失往往会造成项目范围扩张、成本增加&#xff0c;造成延期交付和风险增加等问题&#xff0c;直接影响客户满意度。而隐藏需求的挖掘和确认&#xff0c;有利于优化项目范围&#xff0c;提升产品质量&#xff0c;增强团队信心。 因此&am…

录屏工具哪款好用?精选3款,宝藏分享

“想问一下大家平时都是用哪一款录屏软件啊&#xff1f;感觉现在市面上的录屏软件特别多&#xff0c;但是却一直找不到适合自己的&#xff0c;想看一下大家的宝藏录屏工具都有哪些&#xff0c;求推荐&#xff01;” 在数字化时代的浪潮中&#xff0c;录屏工具如同一把神奇的画…

【机器学习】Scoring Model Scores: 理解、设计与优化评分模型

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Scoring Model Scores: 理解、设计与优化评分模型引言1. 评分模型的定义与重要性…

什么是大数据信用?它的作用有哪些?怎么查询大数据?

在金融行业中&#xff0c;风险管理是至关重要的一环。传统的信用评估方法主要基于借款人的财务状况和信用历史&#xff0c;但这些信息往往无法全面反映借款人的信用状况。大数据信用的出现为金融风控提供了新的解决方案。 首先&#xff0c;大数据信用可以为金融机构提供更全面的…

ns3-gym入门(三):在opengym基础上实现一个小小的demo

因为官方给的"opengym""opengym-2"这两个例子都很简单&#xff0c;所以自己改了一个demo&#xff0c;把reward-action-state相互影响的关系表现出来 一、准备工作 在ns3.35/scratch目录下创建一个文件夹&#xff1a; &#xff08;后续的运行指令后面都需要…

Excel办公技巧:制作二级联动下拉菜单

分享制作二级联动下拉菜单的方法&#xff0c;即使数据有增删&#xff0c;菜单也能自动更新&#xff01; 可以通过先定义名称&#xff0c;再结合数据验证&#xff0c;来做二级联动下拉菜单。 1. 准备数据 首先&#xff0c;我们需要准备好要进行二级联动下拉菜单的数据&#xff…

【单目3D检测】smoke(1):模型解析

SMOKE是纵目科技在2020年提出的单目3D检测新方法&#xff0c;论文展示了一种新的3D目标检测方法&#xff0c;该方法通过将单个关键点估计与回归3D变量相结合来预测每个检测到的目标3D bounding box。SMOKE延续了centernet的key-point做法&#xff0c;认为2d检测模块是多余的&am…

【经验分享】关于静态分析工具排查 Bug 的方法

文章目录 编译器的静态分析cppcheck安装 cppcheck运行 cppcheck 程序员的日常工作&#xff0c;不是摸鱼扯皮&#xff0c;就是在写 Bug。虽然这是一个梗&#xff0c;但也可以看出&#xff0c;程序员的日常一定绕不开 Bug。而花更少的时间修复软件中的 Bug&#xff0c;且不引入新…

Spring Web MVC入门(2)(请求2)

目录 1.传递JSON数据 传递JSON对象 2.获取URL中的参数PathVariable 3.上传文件RequestPart 4.获取Cookie/Session (1)获取Cookie 简洁获取Cookie (2)获取Session Sesson读取 简洁获取Session(1) 简洁获取Session(2) 5.获取Header 简洁获取Header 1.传递JSON数据 J…

Python中的数据结构:五彩斑斓的糖果盒

在Python编程的世界里&#xff0c;数据结构就像是一个个五彩斑斓的糖果盒&#xff0c;每一种糖果都有其独特的味道和形状。这些多姿多彩&#xff0c;形状和味道各异的糖果盒子包括了&#xff1a;List&#xff08;列表&#xff09;、Tuple&#xff08;元组&#xff09;、Diction…

深度学习落地实战:识别火车票信息

前言 大家好&#xff0c;我是机长 本专栏将持续收集整理市场上深度学习的相关项目&#xff0c;旨在为准备从事深度学习工作或相关科研活动的伙伴&#xff0c;储备、提升更多的实际开发经验&#xff0c;每个项目实例都可作为实际开发项目写入简历&#xff0c;且都附带完整的代…

本地多模态看图说话-llava

其中图片为bast64转码&#xff0c;方便json序列化。 其中模型llava为本地ollama运行的模型&#xff0c;如&#xff1a;ollama run llava 还有其它的模型如&#xff1a;llava-phi3&#xff0c;通过phi3微调过的版本。 实际测试下来&#xff0c;发现本地多模型的性能不佳&…

【数智化案例展】某省会城市——轨道交通线网云平台建设

‍ 逸迅科技案例 本项目案例由逸迅科技投递并参与数据猿与上海大数据联盟联合推出的《2024中国数智化转型升级创新服务企业》榜单/奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 本项目将打造一个先进的线网指挥中心大数据平台&#xff0c;它将作为这座城市轨道…

钡铼Profinet、EtherCAT、Modbus、MQTT、Ethernet/IP、OPC UA分布式IO系统BL20X系列耦合器

BL20X系列耦合器是钡铼技术开发的一款用于分布式I/O系统的设备&#xff0c;专为工业环境下的高速数据传输和远程设备控制而设计&#xff0c;支持多种工业以太网协议&#xff0c;包括Profinet、EtherCAT、Modbus、MQTT、Ethernet/IP和OPC UA等。如果您正在考虑部署BL20X系列耦合…