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站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的,绽…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...
LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考
目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
报错 找到package.json文件 找到这个修改成 "lint": "eslint --fix --ext .js,.vue src" 为elsint有配置结尾换行符,最后运行:npm run lint --fix...
