Linux(十)线程安全 上
目录
一、概念
二、互斥锁实现互斥
三、条件变量实现同步
银行家算法
生产者与消费者模型
一、概念
概念:在多线程程序中,如果涉及到了对共享资源的操作,则有可能会导致数据二义性,而线程安全就指的是,就算对共享资源进行操作也不会导致数据二义
实现:如何实现多线程中对共享资源的操作不会出问题
互斥:通过同一时间对资源访问的唯一性,保证访问安全
互斥的实现:互斥锁(读写锁、自旋锁...)
同步:通过条件控制,让多执行对资源的获取更加合理
同步实现:条件变量、信号量
二、互斥锁实现互斥
实现对共享资源的唯一访问
1)互斥锁实现互斥的原理
本质:就是一个1/0计数器,通过0/1标记资源的访问状态(0-不可访问、1-可访问)
在访问资源之前进行加锁(通过状态判断是否可访问,不可访问则阻塞)
在访问资源之后进行解锁(将资源状态置为可访问状态,唤醒其他阻塞的线程)
也有另一种理解:访问资源之前加锁(获取锁资源-获取不到就阻塞)
访问完毕解锁(归还锁资源)
多个线程想要实现互斥,就必须访问同一个锁才可以,也就意味着锁也是一个共享资源
互斥锁的操作本身必须是安全的:互斥锁本身计数器的操作时原子操作
2)互斥锁如何实现自身操作安全的原理
我们知道内存与cpu之间的数据传输:当进行加锁操作时,先将锁中的1置入cpu(先将变量数据从内存加载到cpu)然后才能从cpu中对数据进行处理(转化为0)然后在从cpu中将数据加载到内存指定位置(完成将1替换为0)
然而这样一种操作就会产生问题,如果1从内存加载到cpu还没有将处理后的0加载回内存时,这时切换其他线程运行访问到的锁资源仍然为1,就会继续进行加锁,这无疑是致命的。
所以对于锁资源本身来说,计数器的操作必须是原子性的才可以。
有个指令类似于exchange,功能是交换指定寄存器与内存中的数据
1、先将指定寄存器中的值修改为0,
2、将寄存器与内存中的数据进行互换
3、判断是否符合获取锁的条件或者说判断是否能够加锁
这样使用exchange指令就可以实现计数器操作为原子操作。
3)接口
互斥锁类型变量 pthread_mutex_t
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
mutex:互斥锁变量的地址 attr:互斥锁变量属性(通常置NULL)
访问资源前加锁
int pthread_mutex_lock(pthread_mutex_t *mutex) 阻塞加锁(老实人)
int pthread_mutex_trylock(pthread_mutex_t *mutex) 非阻塞加锁(海王)
访问资源后解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
释放销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER (这种初始化不需要销毁)
4)代码模拟
当不使用互斥锁线程之间对同一变量的访问情况如何?
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>int ticket = 100;
void *Scalper(void *arg)
{while(1){if(ticket > 0){usleep(10);printf("%p:我抢到了第 %d 号票\n",pthread_self(), ticket);ticket--;}else{printf("%p:票完了,我的工作结束了\n", pthread_self());break;}}return NULL;
}
int main()
{pthread_t tid[4];for(int i = 0; i < 4; i++){int ret = pthread_create(&tid[i], NULL, Scalper, NULL);if(ret != 0){perror("create error");return -1;}}for(int i = 0; i < 4; i++){pthread_join(tid[i], NULL);}return 0;
}
发现出现了很多意外情况,重复、负数票号,
为什么呢?就是因为每个线程之间访问同一变量,一个线程还正对变量进行操作的时候另一个线程也进行操作,于是就发生了同一变量的多次出现。
使用互斥锁保护临界区
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>int ticket = 100;
void *Scalper(void *arg)
{while(1){pthread_mutex_lock(arg); // 访问之前加锁if(ticket > 0){usleep(10);printf("%p:我抢到了第 %d 号票\n",pthread_self(), ticket);ticket--;pthread_mutex_unlock(arg); // 在所有有可能退出的地方解锁}else{printf("%p:票完了,我的工作结束了\n", pthread_self());pthread_mutex_unlock(arg); // 在所有有可能退出的地方解锁break;}usleep(1);}return NULL;
}
int main()
{pthread_mutex_t mutex; // 定义锁变量 mutexpthread_mutex_init(&mutex, NULL); // 初始化锁资源pthread_t tid[4];for(int i = 0; i < 4; i++){int ret = pthread_create(&tid[i], NULL, Scalper, &mutex); // 注意锁资源通过线程创建第四个参数传入入口函数内if(ret != 0){perror("create error");return -1;}}for(int i = 0; i < 4; i++){pthread_join(tid[i], NULL);}return 0;
}
5)死锁
死锁是一种状态,是一种因为资源争抢不当导致程序流程卡死无法继续向下推进的状态。
多个线程对锁资源的争抢使用不当导致程序流程卡死,无法继续向下推进的状态
1、加锁之后没有释放就退出,导致其他线程获取不到锁资源卡死
2、多锁使用时,加锁顺序不当,线程1加锁顺序为AB,线程2加锁顺序为BA
发生死锁的必要条件
① 互斥条件 同一时间一把锁只能被一个线程所获取到
② 不可剥夺条件 一个线程加的锁,只能自己释放,其他线程无法释放
③ 请求与保持 一个线程请求了A锁之后请求B锁,如果请求不到就不会释放A锁
④ 环路等待 线程1加了A锁后请求B锁,线程2加了B锁后请求A锁
死锁的预防:破坏死锁产生的必要条件
① 和 ② 无法修改
写代码时要注意:
1、线程之间的加解锁顺序尽量一致 -- 尽可能预防环路等待
2、采用非阻塞加锁,如果加不上锁,则把已经加上的锁释放 -- 破坏请求与保持
(请求不到新的,则释放已有的)
避免:银行家算法、死锁检测算法……
银行家算法
http://t.csdn.cn/1YxZj
三、条件变量实现同步
1)概念
同步:通过条件控制,保证资源访问的合理性
条件变量:主要是一个pcb等待队列,以及唤醒和阻塞线程的接口
原理:
线程1 获取资源时进行判断,如果线程不符合资源获取条件,则调用阻塞接口进行阻塞
线程2 促使资源获取条件满足之后(生产资源),通过唤醒接口唤醒阻塞的线程
条件变量需要搭配互斥锁来使用
举例:
顾客 与 厨师 (消费者与生产者)
顾客来到柜台,看到柜台有饭则吃饭,否则阻塞
厨师来到柜台,看到柜台上没有饭则做饭,否则阻塞
顾客:
0、加锁(关门)
1、访问柜台有没有饭
有饭则吃饭,没有饭就阻塞
(这里阻塞时需要进行解锁,否则厨师就无法访问柜台,也就无法做饭,产生死锁)
阻塞则需先解锁,再阻塞,被唤醒之后再加锁
2、吃饭
3、吃完了,再来一碗 唤醒厨师
4、解锁
厨师:
0、加锁
1、访问柜台有没有饭
没饭则做饭,有饭则阻塞
(这里是有饭就需要解锁,让顾客能够吃饭,否则会死锁)
阻塞需先解锁,再阻塞,被唤醒后加锁
2、做饭
3、做好了,唤醒顾客
4、解锁
2)接口
定义条件变量:
pthread_cond_t 条件变量的变量类型
初始化条件变量:
pthread_cond_t cond_init (pthread_cond_t *cond, pthread_condattr_t *attr);
阻塞接口:条件变量是搭配互斥锁一起使用的,就体现在阻塞这一步
int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex) -- 阻塞接口
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,
struct timespec *t ) -- 有时长限制的阻塞
唤醒接口:
int pthread_cond_signal(pthread_cond_t *cond) 唤醒至少一个阻塞队列中的线程
int pthread_cond_broadcast(pthread_cond_t *cond) 唤醒等待队列中所有的线程
销毁接口:
int pthread_cond_destroy(pthread_cond_t *cond)
3)模拟实现
#include<stdio.h>
#include<pthread.h>int counter = 0; // 定义柜台状态 0——没饭 1——有饭
pthread_mutex_t mutex; // 初始化锁
pthread_cond_t cond; // 初始化条件变量void* customer(void *arg)
{while(1){pthread_mutex_lock(&mutex); // 先加锁if(counter==0){pthread_cond_wait(&cond, &mutex); // 没饭则解锁 并阻塞,等待唤醒,唤醒后加锁}printf("真好吃,再来一碗\n");counter = 0;pthread_cond_signal(&cond); // 吃完了唤醒厨师做饭pthread_mutex_unlock(&mutex); // 解锁}
}void* cook(void *arg)
{while(1){pthread_mutex_lock(&mutex); // 加锁if(counter == 1){pthread_cond_wait(&cond, &mutex); // 有饭则 解锁并阻塞,等待唤醒,唤醒后加锁}printf("你的饭好了\n");counter = 1;pthread_cond_signal(&cond); // 饭做好了唤醒顾客吃饭pthread_mutex_unlock(&mutex); // 解锁}
}int main()
{pthread_mutex_init(&mutex, NULL); // 初始化定义mutex 和 condpthread_cond_init(&cond, NULL);pthread_t cook_tid; // 初始化定义线程ID(顾客和厨师)pthread_t cus_tid;int ret;ret = pthread_create(&cook_tid, NULL, cook, NULL); // 分别创建对应线程if(ret != 0){perror("create error");return -1;}ret = pthread_create(&cus_tid, NULL, customer, NULL);if(ret != 0){perror("create error");return -1;}pthread_join(cook_tid, NULL); // 执行线程等待pthread_join(cus_tid, NULL);pthread_mutex_destroy(&mutex); // 执行mutex与cond的销毁pthread_cond_destroy(&cond);
}
实现四个顾客四个厨师
在同步实现的代码中,如果存在多种角色,就应该定义多个条件变量,各自处于各自的pcb等待队列中。
#include<stdio.h>
#include<pthread.h>int counter = 0;
pthread_mutex_t mutex;
pthread_cond_t cond_cus; // 使用不同的条件变量,防止死锁
pthread_cond_t cond_cook;
void* customer(void *arg)
{while(1){pthread_mutex_lock(&mutex);while(counter <= 0){pthread_cond_wait(&cond_cus, &mutex);}printf("真好吃,再来一碗: %d\n",counter);counter--;pthread_cond_signal(&cond_cook);pthread_mutex_unlock(&mutex);}
}void* cook(void *arg)
{while(1){pthread_mutex_lock(&mutex);while(counter > 0){pthread_cond_wait(&cond_cook, &mutex);}printf("你的饭好了:%d\n", counter);counter++;pthread_cond_signal(&cond_cus);pthread_mutex_unlock(&mutex);}
}int main()
{pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_cus, NULL);pthread_cond_init(&cond_cook, NULL);pthread_t cook_tid[4]; // 定义四个顾客、四个厨师pthread_t cus_tid[4];int ret;for(int i = 0; i < 4; i++) // 创建这八个线程{ret = pthread_create(&cook_tid[i], NULL, cook, NULL);if(ret != 0){perror("create error");return -1;}ret = pthread_create(&cus_tid[i], NULL, customer, NULL);if(ret != 0){perror("create error");return -1;}}pthread_join(cook_tid[0], NULL);pthread_join(cus_tid[0], NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_cook);pthread_cond_destroy(&cond_cus);
}
生产者与消费者模型
生产者与消费者模型http://t.csdn.cn/GvZlZ
相关文章:

Linux(十)线程安全 上
目录 一、概念 二、互斥锁实现互斥 三、条件变量实现同步 银行家算法 生产者与消费者模型 一、概念 概念:在多线程程序中,如果涉及到了对共享资源的操作,则有可能会导致数据二义性,而线程安全就指的是,就算对共享…...

CRM系统能给企业带来什么? CRM系统推荐
什么是CRM系统? CRM系统(又称客户关系管理系统)是一个以客户为核心的管理软件,能有效改善企业与现有客户的关系,且帮助企业寻找新的潜在客户,并赢回以前老客户。 CRM系统能给企业带来什么? C…...

ESP32设备驱动-LED控制器生成PWM信号
LED控制器生成PWM信号 文章目录 LED控制器生成PWM信号1、LED控制器介绍2、软件准备3、硬件准备4、代码实现PWM 是一种在数字引脚上获取类似模拟信号的方法。PWM实际上是一个在高电平和低电平之间切换的方波信号,在 0V 和 3.3V 之间。 当信号为 HIGH 和 LOW 时,这种连续的 HIG…...

秒杀项目之网关服务限流熔断降级分布式事务
目录一、网关服务限流熔断降级二、Seata--分布式事务2.1 分布式事务基础2.1.1 事务2.1.2 本地事务2.1.3 分布式事务2.1.4 分布式事务场景2.2 分布式事务解决方案2.2.1 全局事务可靠消息服务2.2.2 最大努力通知2.2.3 TCC事事务三、Seata介绍四、 Seata实现分布式事务控制4.1 案例…...

OSS(Object Storage Service)进行上传图片,下载图片(详细看文档可以完成操作)
文章目录1.单体前后端项目上传1.上传流程2. BuckName 和EndPoint3. AccessKey 和Access Secret(创建RAM(Resource Access Manage)的子账号,然后可以获得Accesskey和Acess Secret)3.根据创建的子账号分配OSS的所有权限(可以对文件进行上传&…...

4年功能测试经验,裸辞后找不到工作怎么办?
软件测试四年,主要是手动测试(部分自动化测试和性能测试,但是用的是公司内部自动化工具,而且我自动化方面是弱项。) 现在裸辞三个月了,面试机会少而且面试屡屡受挫。总结就是自动化,性能&#…...

类和对象(中)(二)
类和对象(中)(二)1.赋值运算符重载1.1运算符重载1.2赋值运算符重载1.3前置和后置重载2.const成员3.取地址及const取地址操作符重载🌟🌟hello,各位读者大大们你们好呀🌟🌟…...

Hadoop自动安装JDK
目录 1、使用xftp工具 在opt目录下创建install和soft文件 2、使用xftp工具 将压缩包上传到install文件 3、编写shell脚本 3.1、创建目录来放shell脚本 3.2、创建autoinsatll.sh文件并修改权限 3.3、编写autoinsatll.sh 文件 4、 运行 5、测试 1、使用xftp工具 在opt目…...

Springboot+Vue java毕业论文选题管理系统
在分析并得出使用者对程序的功能要求时,就可以进行程序设计了。如图展示的就是管理员功能结构图。 系统实现前端技术:nodejsvueelementui 前端:HTML5,CSS3、JavaScript、VUE 系统分为不同的层次:视图层(vue页面&#…...

面向战场的cesium基础到进阶的案例展示(我相信VIP总是有原因的)
cesium 前置说明(友情提示,关注重点代码,其他影响复现的都可以删除或者替换数值解决) 这里面用到了cesium的模型加载、图片加载、着色器、实时改变模型状态、模型删除等知识点,这需要你自己去观摩下述会包含所有相关代码,他们的联系其实在代码中能看到(比如飞机操作类会…...

XXL-JOB 分布式任务调度平台
目录 一、简介 1.1 概述 1.2 社区交流 1.3 特性 1.4 架构设计 1.4.1 设计思想 1.4.2 系统组成 1.4.3 调度模块剖析 1) quartz的不足 1.5、同类型框架对比 1.6 下载 1.6.1 文档地址 1.7 环境 二、XXL-JOB安装部署 2.1、配置部署“调度中心” 1&…...

通过 指针 引用 多维数组 详解
目录 一:回顾多维数组地址知识 二:二维数组的有关指针 三:指向数组元素的指针变量 四:用指向数组的指针作为函数参数 首先简单来讲,指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。下面…...

【Linux】宝塔面板 SSL 证书安装部署
宝塔面板 SSL 证书安装部署前言证书下载宝塔配置SSL注意事项前言 前期有讲过Tomcat和Nginx分别部署SSL证书,但也有好多小伙伴们私信我说,帮忙出一期宝塔面板部署SSL证书的教程,毕竟宝塔的用户体量也是蛮大的,于是宠粉的博主&…...

由 GPT 驱动的沙盒,尽情发挥想象力! #NovelAI
一个由 GPT 驱动的沙盒,供用户尽情发挥想象力的空间,会获得怎样的体验?NovelAI NovelAI 是一项用于 AI 辅助创作、讲故事、虚拟陪伴的工具。NovelAI 的人工智能算法会根据用户的方式创建类似人类的写作,使任何人,无论能…...
ubuntu 服务器安装配置VNC访问
ubuntu 如果服务器没有桌面相关图形包,需手动安装下: sudo apt install ubuntu-desktop sudo apt install lightdm VNC安装: 1.安装 在Ubuntu上安装x11vnc,如下: sudo apt-get install x11vnc 2.配置vnc密码 x1…...

【C→C++】打开C++世界的大门
文章目录前言什么是CC的发展史C的重要性1. 使用广泛度2. 工作领域的应用1. C关键字(C98)2. 命名空间2.1 命名空间的定义2.2 命名空间的使用2.3 std命名空间的使用惯例3. C输入&输出3.1 输入输出3.2 说明4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类5. 函数重载5.1 函数重载…...

点云深度学习系列博客(四): 注意力机制原理概述
目录 1. 注意力机制由来 2. Nadaraya-Watson核回归 3. 多头注意力与自注意力 4. Transformer模型 Reference 随着Transformer模型在NLP,CV甚至CG领域的流行,注意力机制(Attention Mechanism)被越来越多的学者所注意,将…...

设置Visual Studio 2022背景图
前言 编写代码时界面舒服,自己喜欢很重要。本篇文章将会介绍VS2022壁纸的一些设置,主题的更改以及如何设计界面。 理想的界面应该是这样的 接下来我们来一步步学习如何将界面设计成这样 一、壁纸插件下载 1.拓展->点击拓展管理 2.右上角搜索backgro…...

1. 用Qt开发的十大理由
用Qt的十大理由 原因最主要的是很多大公司都在用,有钱景。 先来看看各大公司的评价: 奔驰:们用 Qt 开发了绝大部分的UI体验和软件,包括屏幕动画,屏幕间的过渡和小组件。 FORMLABS:凭借Qt的快速迭代&…...

俄罗斯方块游戏代码
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的,绽…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...