Linux多线程-2

news2024/11/23 8:03:52

“我是我境遇里的起步者。” -- 里尔克《布里格手记》

在上一节Linux多线程当中,我们讲述了Linux中线程的概念以及线程的控制内容。这一篇博客承接上次内容,我们来对线程安全进行阐述。

目录

1.线程安全

1.1互斥的实现

1.1.1互斥锁实现互斥

1.1.2互斥锁相关接口

1.1.3互斥锁的死锁情况

1.2同步的实现

1.2.1条件变量实现同步

1.2.2条件变量相关接口

1.2.3信号量实现同步

1.线程安全

在多线程程序当中,当我们涉及到共享资源的操作时,可能会导致数据二义性造成结果不可控现象。那么对于线程安全而言,便是当我们对共享资源进行操作的时候,不会造成数据的二义性。简而言之,线程安全指的是多线程中对共享资源的操作不会出现问题。

那么我们该通过何种方式来保证线程安全,即互斥和同步。

  • 互斥:同一时间执行流对资源的操作唯一,保证访问安全性;
  • 同步:通过条件控制,让多执行流对资源的获取更加合理。

1.1互斥的实现

互斥的实现有较多方式:互斥锁,读写锁,自旋锁……我们详细讲述互斥锁的内容:

1.1.1互斥锁实现互斥

讨论互斥锁的原理之前,首先我们来谈一谈互斥锁的本质:0/1计数器,即通过0和1来标记资源的可访问和不可访问状态。(0-不可访问,1-可访问)

当访问资源之前进行加锁操作(通过状态判断是否可访问,不可访问则阻塞);在访问资源之后进行解锁操作(将资源置为可访问状态,唤醒其他被阻塞的进程)。

对于多线程而言,当我们对资源进行互斥锁管理的时候,就必须保证多个线程访问的是同一个互斥锁(加锁的是同一个共享资源),否则多个线程对于资源的访问时没有收到限制的。

互斥锁的操作本身必须是安全的:互斥锁本身计数器操作是原子操作。那么对于互斥锁本身是如何实现安全的,我们做以下讨论:

在具体的实现当中,一个变量的数据加载到内存之中不是瞬间的,而是需要一个过程。所以万一出现在某一线程对共享资源的访问中,还未曾将计数器的1修改为0时,线程切换,第二个线程要对共享资源进行访问,它会发现共享资源还未被置为不可访问状态,于是它也可以对共享资源进行访问。

就类似于:当一号线程进门之后正准备访问资源和关门,但是线程切换,二号线程发现共享资源的门是开着的,于是它也就进门对共享资源进行操作。

所以为了避免上述问题的出现,我们在线程访问cpu中加入寄存器,并通过exchange操作来交换寄存器和内存中的数据,即:

  1. 先将指定寄存器中的值修改为0;
  2. 将寄存器中与内存的数据进行交换;
  3. 判断相关条件是否能够加锁或者去锁。

此时,当一个线程对共享资源进行访问还没来得及加锁,线程切换后其他线程看到的依旧是0,即其他线程无法进行访问。

1.1.2互斥锁相关接口

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexatter_t *attr);

pthread_mutex_init接口用于实现互斥锁的初始化,其中mutex是互斥锁变量的地址,attr:互斥锁变量属性,通常置为NULL。

int pthread_mutex_lock(pthread_mutex_t *mutex);

 pthread_mutex_lock是阻塞加锁接口,如果加不上锁,则一直等待,直到加锁成功;

int pthread_mutex_trylock(pthread_mutex_t *mutex);

pthread_mutex_trylock是非阻塞加锁接口,如果加锁不成功,则立即报错返回。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 pthread_mutex_unlock用于访问资源完毕时解锁。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_destroy用于释放资源时销毁锁。 

了解完接口之后,我们照例来进行代码演示:

 我们对上述存在多线程的代码编译并执行我们会发现:

怎么会存在第100位数据被重复获取,这就联系到了我们今天的内容,即线程安全。上述代码展示出来的结果暴露了一个问题,即共享资源被多线程访问的不安全问题,那么结合互斥锁的内容,我们对其进行改进和优化:将判断共享资源剩余和获取共享资源保护起来,中间不可被打断。

加入互斥锁后,需要注意在任何线程可能退出的位置都需要解锁。并且在一个线程获取完资源之后,我们加入ulseep保证时间片的分配,否则结合加锁解锁操作,始终会是一个线程对资源进行访问。最后编译执行得到的结果如下:

很好的展现了互斥锁在线程安全中的作用。 

1.1.3互斥锁的死锁情况

死锁是多个线程对锁资源的争抢不当导致程序流卡死,程序无法继续向前推进的情况。

死锁产生有两种简单的情况,即:加锁之后未解锁导致其他线程无法访问资源导致程序卡死,或是多锁使用时,加锁顺序和解锁顺序不一致。

但实际使用过程中死锁的产生原有远不止上述简单的两种,更可能是以下死锁产生的必要条件:

  1. 互斥条件 -- 同一时间一个锁只能被一个线程获取,多个线程无法访问同一个锁;
  2. 不可剥夺条件 -- 一个线程加的锁,只能由该线程解锁,其他线程无法释放;
  3. 请求与保持条件 -- 线程加了A锁之后又去请求B锁,如果B锁无法请求也不释放A锁;
  4. 环路等待条件 -- 线程1加了A锁请求B锁,线程2加了B锁请求A锁。

死锁的预防:破坏死锁产生的必要条件

  1. 多个线程加锁和解锁的顺序保持一致,尽可能避免环路条件的产生;
  2. 采用非阻塞加锁,如果无法成功加锁,则将已经加锁成功的释放。 -- 破坏请求与保持条件

死锁的避免:当死锁已经产生的解决方案

  1. 银行家算法;
  2. 死锁检测算法;
  3. ……

1.2同步的实现

同步是为了让资源的分配更加合理,但是资源分配的合理不会保证资源分配的安全,这是我们需要注意的点,具体的同步实现方式有:条件变量、信号量……

1.2.1条件变量实现同步

条件变量主要是提供一个pcb等待队列,以及阻塞和唤醒阻塞线程的接口。其实现原理如下:

  1. 线程1对资源获取条件进行判断,若线程不满足获取条件,则调用阻塞接口阻塞线程;
  2. 线程2促使资源获取条件满足之后,通过唤醒接口唤醒被阻塞的线程。

值得注意的是,一个线程是否满足获取资源条件或是可以唤醒被阻塞线程的条件,需要程序员我们自己来判断。而当在多个线程操作中,条件操作本质上是一个临界资源的操作,那么便需要加锁来进行保护。

所以总而言之,条件变量是和互斥锁搭配进行使用的。

1.2.2条件变量相关接口

定义条件变量的变量类型:pthread_cond_t 

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr *attr);

pthread_cond_init是初始化接口,其中cond是条件变量的地址,attr是条件变量的属性,一般置为NULL。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_wait是阻塞等待接口,如果等不到资源则一直等待。

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *t);

pthread_cond_timedwait是有时长的阻塞等待接口,如果在t时间内未等到资源,则结束。

返回值为:ETIMEDOUT则表示时间结束时仍未获取到资源。

int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_signal是唤醒至少一个阻塞队列中的线程。

int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_broadcast是唤醒等待队列中的所有线程。

int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_destroy用于释放资源。

在大致了解完相关接口之后,我们照例来通过代码对内容进行实践。

对cond.c编译并执行可到结果如下:

我们可以很直观的看出,资源的获取和生产是交替打印的,这便很好的体现了条件变量的同步,即线程交替运行。

1.2.3信号量实现同步

对于这部分内容,我们在Linux进程通信-3这便博客中以及提及到,所以在此不做多余赘述。

 

 

 

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

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

相关文章

启动 Ethereum(上海) 主网全节点

问题描述 采用最新的geth版本之后,按照之前的方法启动geth主网节点会出现如下问题: Post-merge network, but no beacon client seen. Please launch one to follow the chain!问题原因 The above message is emitted when Geth is run without a conse…

PyTorch深度学习实战 | 神经网络的优化难题

即使我们可以利用反向传播来进行优化,但是训练过程中仍然会出现一系列的问题,比如鞍点、病态条件、梯度消失和梯度爆炸,对此我们首先提出了小批量随机梯度下降,并且基于批量随机梯度下降的不稳定的特点,继续对其做出方…

SpringBoot集成Dubbo启用gRPC协议

文章目录前言项目结构代码示例父工程api moduleservice module注意事项区别本文记录下SpringBoot集成Dubbo启用gRPC协议,以及与原生 gRPC 在代码编写过程中的区别。 下面还有投票,帮忙投个票👍 前言 Dubbo 在 2.7.5 版本开始支持原生 gRPC 协…

【软件测试】浅了解什么是软件测试及开发测试模型

目录 1.什么是软件测试? 2.什么是需求? 3.什么是测试用例 4.什么是软件错误(bug)? 5.开发模型和测试模型 5.1软件的生命周期 5.2瀑布模型 5.3螺旋模型 5.4软件测试V模型 5.5软件测试w模型 6.软件测试的生命周…

Redis第二十九讲 Redis集群发布订阅模式以及Redis集群事务

Redis集群状态下的发布订阅 在Redis的几个基本数据结构介绍中,有讲过List数据结构,可以使用List的阻塞特性实现订阅消费,关于Redis的底层数据结构可以参考我的这篇博客:Redis第六讲 Redis之List底层数据结构实现 底层数据结构基本操作可以看我的这篇博客,Redis第十五讲 R…

【从零到Offer】- 泛型

泛型是个啥 ​ Java 泛型是 JDK 5 中引入的一个新特性,其提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 ​ 通过将数据类型参数化&#xf…

用opencv+playwright过滑动验证码

目录 梳理思路 编写代码 总结与提高 在本节,我们将使用opencv和playwright这两个库通过QQ空间的滑动验证码。 梳理思路 1. 使用playwright打开浏览器,访问qq空间登录页面。 2. 点击密码登录。 3. 输入账号密码并点击登录。 4. 出现滑动验证码图片后…

大型语言模型综述,非常详细,格局打开!A Survey of Large Language Models

大型语言模型综述,非常详细,格局打开!A Survey of Large Language Models 返回论文和资料目录 论文地址 项目地址 1.导读 讲得通俗易懂,且格局拉满!基本覆盖了自ChatGPT以来的AI比较火的事件,还多次提到…

AI 模型首次有了国家标准!头部大厂参与编制,辐射 AMD

3月17日,国内首个面向人工智能生成式模型的国家标准正式公开,并向社会征求意见。 该标准全称为《信息技术神经网络表示与模型压缩第一部分:卷积神经网络》 (GB/T 42382.1-2023&a…

【Linux系统下安装JDK】

一,linux下载JDK最方便快捷的方式:yum 1,执行下方命令,查看可安装java版本。 yum -y list java*2,选择一个进行安装,带-devel的安装的是jdk,而不带-devel的安装的是jre 3,安装命令…

【springBoot篇1】概念、创建和运行

目录 一、什么是springBoot?为什么要学springBoot springBoot的优点:(5点) 优点1:快速集成框架 优点2:内置了Tomcat容器 优点3:快速部署项目 优点4:少配置,多注解 优点5:支持更…

机器学习中的公平性

文章目录机器学习公平性评估指标群体公平性指标个人公平性指标引起机器学习模型不公平的潜在因素提升机器学习模型公平性的措施机器学习公平性 定义: 机器学习公平性主要研究如何通过解决或缓解“不公平”来增加模型的公平性,以及如何确保模型的输出结果…

生信刷题之ROSALIND——Part 1

目录写在前面1、Counting DNA NucleotidesProblemSample DatasetSample OutputCodeOutput2、Transcribing DNA into RNAProblemSample DatasetSample OutputCodeOutput3、Complementing a Strand of DNAProblemSample DatasetSample OutputCodeOutput4、Rabbits and Recurrence…

Android操作系统介绍

目录 Android 名词 Android LOGO 体系架构 Android系统架构 Linux 内核 硬件抽象层(HAL) Android Runtime 原生C/C库 Java API框架 系统应用 应用组件 活动 (Activity) 服务 (Service ) 广播接收器 &…

BLOOM模型结构详解

《BLOOM: A 176B-Parameter Open-Access Multilingual Language Model》 论文地址: https://arxiv.org/pdf/2211.05100.pdf 代码地址: transformers库-modeling_bloom.py BigScience 官方提供的代码链接并没有找到完整的模型实现代码,只有提示说模型结构代码是在 Megatron 的…

JS 中深拷贝的几种爱恨情仇

页面开发中,经常会碰到需要对数据进行某些处理操作,又不想影响原先的数据,所会经常将数据进行拷贝,当然这里指的是深拷贝。 深拷贝和浅拷贝的区别? 深拷贝通通俗点来讲呢,其实就是不管当前要操作的数据层级…

目标检测算法——YOLOv5/v7/v8改进结合涨点Trick之Wise-IoU(超越CIOU/SIOU)

超越CIOU/SIOU | Wise-IoU助力YOLO强势涨点!!! 论文题目:Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism 论文链接:https://arxiv.org/abs/2301.10051 ​ 近年来的研究大多假设训练数据中的…

Java实现发送邮件(定时自动发送邮件)

系列文章目录 Redis缓存穿透、击穿、雪崩问题及解决方法Spring Cache的使用–快速上手篇分页查询–Java项目实战篇全局异常处理–Java实战项目篇 该系列文章持续更新,更多的文章请点击我的主页查看哦! 文章目录 目录 系列文章目录 文章目录 前言 一…

算法训练第五十七天 | 647. 回文子串、516.最长回文子序列、动态规划总结篇

动态规划part17 647. 回文子串题目描述思路暴力解法动态规划双指针法 516.最长回文子序列题目描述思路 动态规划总结篇动划基础背包问题系列打家劫舍系列股票系列子序列系列总结 647. 回文子串 题目链接:647. 回文子串 参考:https://programmercarl.com…

hot100:数组——31、33

31. 下一个排列 思路:其实这道题的意思就是,简单地说,就是找到一个比现有的给出的数组代表的值大的最小的数 比如给出的数组是[1,2,3],它代表的数值是123,现有的元素组成的数值中,比123大的有很多&#xf…