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站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的,绽…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...