Linux之线程及线程安全详解

前言:在操作系统中,进程是资源分配的基本单位,那么线程是什么呢?线程是调度的基本单位,我们该怎么理解呢?

目录

一,线程概念理解

二,Linux里面的线程原理

三,为什么要有线程

四,线程相关接口

1)线程创建

2)获取本线程ID

3)线程等待

4)线程取消

5)线程退出

五,多线程安全

1,互斥锁的原理

 2,互斥锁的使用

3,锁带来的饥饿问题

4,信号量

六,线程安全条件

1,常见的线程安全情况

2,常见的线程不安全情况

3,死锁


一,线程概念理解

现在我们举一个例子:我们以家庭为单位,将家庭看作一个整体,假如分配房子,车子等社会资源,这些资源被分配是以家庭为单位,这就类似于进程,每个进程执行一个大任务,进程之间的联系并不紧密并且相对独立。线程则类似与家庭里面的每一个人,家里面有许多为了完成大任务而拆分出来的小任务,比如孩子要上学,父母要上班,还有家务,家庭里面的每一个人有关联但也有各自不同的小任务,家庭里面的每一个人都共享所有资源,比如电视剧,客厅,车子——进程被分配的资源,但是同时它们也有自己的私人空间,线程同样如此,但线程的私人空间是

线程 ID
一组寄存器
errno
信号屏蔽字
调度优先级
为什么线程要有自己的私人空间呢?因为即使每个人都是为这个家庭(进程)做事,但是每个人做的事情不一样,我们需要一些私人数据才能完成不同的任务,而且为了区分不同的家庭成员(线程)我们也需要对他们进行起名(编号),这也是私人数据。

二,Linux里面的线程原理

大家有没有发现,线程和进程的功能其实有点类似,比如进程是执行一个复杂的大任务,而线程则是执行大任务里面拆分出来的小任务,并且它们都有自己的栈,共享进程的资源那我们可以使用进程的PCB来复用代替线程的TCB吗?答案是可以的,这样子不仅提高了代码的复用率,降低了编写的难度,让代码结构和维护变得更加简单,LinuxTCP的结构体就和PCB一样,但也可以自己编写一个独立的TCB,比如:windows系统。但也有所不同,进程号是标识进程唯一性的编号,而线程号则是一个地址——线程地址。
Linux里面的线程被称作轻量级进程,我们需要理清楚线程(轻量级进程)和进程之间的关系,进程是一组线程集合,一个进程最少有一个线程,线程则是进程里面的一个执行流(执行小任务)

三,为什么要有线程

大家可能很好奇为什么有进程了还要有线程,一个CPU一个时间段执行一个指令吗?线程虽称并行执行流,但底层还是不能同时执行,也得排队。

现在举一个例子:有一个进程A需要执行一个任务,从外设输入字符串并且打印到屏幕上,并且要计算一些加减乘除。

如果只有一个进程我们只能顺序执行,也就是当输入输出的时候我们不能干其他事必须等待,而IO流的速度是很慢的,如果我们一直等待不就把CPU的资源浪费了吗?如果我们利用线程呢?一个线程负责IO流,一个线程负责计算,这样子当我们进行IO操作的时候,我们可以把它挂起,利用CPU去进行计算,当IO操作完成再唤醒执行这个线程,这样子CPU的利用率就提高了,执行效率也提高了。

但是有人可能有疑问,我们为什么不切换到下一个进程,等到IO操作完成再唤醒这个进程呢?这就涉及到了开销的问题,进程之间是相互独立的,我们切换是需要进行保存现场等工作,这样子不断的切换开销是很大的,而线程它们之间的切换的开销小的很多,他们的页表,数据都是共享的。

那么线程适合什么样的场景使用,线程是越多越好吗?
线程适合IO操作较多的场景,计算流操作效果比较差,因为CPU的算力是有限的,同一时间只允许一个计算任务。

线程并不是越多越好,因为线程也是有开销的,例如TCB结构体,栈,对这些进行管理也要开销。

四,线程相关接口

在Linux里面实际上是没用线程的,只有轻量级进程,但是为了迎合主流,Linux还是对轻量级进程进行了包装成了线程库,因此在编译时要连接原生线程库,在g++指令后面加上  -lpthread,例如:

g++ -o test.o test.cpp -std=c++11 -lpthread
线程接口使用类似于进程间通信的接口,首先我们要认识一下区分线程的唯一标识符,也就是线程地址
pthread_t 

底层实际上就是一个unsigned int 

1)线程创建

 第一个参数thread会通过指针返回创建线程的ID,第二个参数大部分情况是NULL,用来调整线程的属性,第三个参数是线程执行的函数,第四个参数是执行函数的参数。成功返回0,失败返回错误编号。

void* task(void* arg){
int* i=(int*)arg;
cout<<"这是一个线程任务:"<<*i;
}
pthread_t tid;
int i=1;
pthread_create(&tid,NULL,task,(void*)i);
2)获取本线程ID

没有参数,返回值就是本线程的ID。

3)线程等待
第一个参数是等待线程ID,第二个是对线程进行管理的参数,一般默认NULL。
这个函数的作用是等待一个线程结束,成功返回0,失败返回错误编号,在这个线程结束前这个函数不会结束。
pthread_t tid=pthread_self();
pthread_join(tid,NULL);

4)线程取消
函数参数是取消线程的ID,操作成功返回0。
pthread_t tid=pthread_self();
pthread_cancel(tid);
5)线程退出

这个作用于pthread_exit作用效果类似,但是只能退出本线程,不能退出指定线程。

五,多线程安全

大家有没有想过多线程执行会不会带来安全问题,答案是必然的,为什么呢?因为线程简单切换会发生在如何不是原子代码执行的时候(原子性是指代码执行不会被中断,要么不开始,一开始就必须执行完,不存在中间状态),

int tictik=100;
void* RobTictik(void* arg){
while(titck>0){
cout<<"线程:"<<pthread_self()<<"抢到了票"<<tictik--<<endl;
}
}

如果所有线程执行这个函数,很有可能就会出现tictik最后小于0的情况,也就是最后卖出了多于100张的票,为什么呢?假如tictik已经是1了,线程A刚刚进入还未来得及打印将tictik打印就切换到了线程B,就会出现多卖票的情况。

那我们有什么办法解决吗?

1,互斥锁的原理

互斥锁是什么呢?人如其名它的功能类似于一把锁,你进去时候加上一把锁,当别人试图进来的时候就会因为没有钥匙而无法进来,你出去的是就把锁换回去,让其他想进来的人竞争这把钥匙。

锁的原理是什么呢?其实挺简单的,就是锁里面本来有一个1,当线程切换的时候线程会把自己的上下文保存,将数据1拿走,其他线程走到这块区域的时候就发现是0无法运行,继续等待抢锁,直到线程将这块区域运行完才会将锁换回去。下面这个图就是类似我讲述的锁原理。

 2,互斥锁的使用

互斥锁的使用需要初始化,然后加锁,解锁。

初始化有两种方式,一种是全局锁,一种是局部锁(作用域)

这是互斥锁的结构体

全局互斥锁初始化

局部互斥锁的初始化 

第一个参数是锁结构体,第二个参数一般填NULL。

加锁

成功返回0,需要注意的是加锁代码是原子性的,防止多个线程进入锁

解锁

成功返回0,注意解锁并不是原子性的,因为解锁时是不是原子性已经不重要了,如果锁已经归还,多线程也只能有一个抢到,如果还未归还不过是让其他线程多等等。

pthread_mutex_init(&_mutex,NULL);pthread_mutex_lock(&p->_mutex);//临界区代码,被保护,原子性 pthread_mutex_unlock(&p->_mutex);

3,锁带来的饥饿问题

互斥锁的抢夺是公平的,但是有一些线程的抢锁能力强,这就会导致一个问题,一个线程长期霸占着锁,其他线程就一直无法运行代码,导致饥饿问题,那有什么解决办法吗?答案是条件变量。

条件变量是什么呢?之前我们举例子所有人抢钥匙开门,现在我们加一个规矩,那就是排队,新来的和出去的只能从后面开始排队,而且这段时间你们都处于休眠,直到轮到你们有人唤醒你们才继续执行。

条件变量使用很类似于互斥锁

条件变量结构体

初始也分全局初始化和局部初始化

全局条件变量初始化

局部条件变量初始化

第一个参数是条件变量结构体,第二个参数一般是NULL。

互斥锁的使用一般是放在互斥锁里面的,如果将线程放入条件队列,会先解锁,然后继续抢锁,因此建议进入互斥锁临界区就先检查是否需要放入条件队列等待

参数一是条件变量结构体,参数二是互斥锁,因为条件变量是需要结合互斥锁使用的。

条件变量的唤醒,我们直到进入条件变量等待队列后是无法自己醒来的,需要使用函数唤醒

唤醒指定条件变量里面的一个线程,成功返回0

唤醒指定条件变量里面的所有线程,成功返回0

破坏条件变量

pthread_mutex_init(&_mutex,NULL);
pthread_cond_init(&cond,NULL);pthread_mutex_lock(&p->_mutex);
while(条件不满足){pthread_cond_wait(&cond,&mutex);
}//临界区代码,被保护,原子性 pthread_mutex_unlock(&p->_mutex);

上面的代码为什么要用循环来判断条件是否满足呢?因为即使抢到锁了条件也不一定满足,如果是if语句就会直接执行接下里的代码,导致线程安全问题 

4,信号量

在Linux里面信号量也是保护线程安全的一种重要手段,一般也是结合互斥锁使用

信号量的原理就是计数器,但是对计数器的操作是原子性的,举个例子,假如盆里面有十个苹果,有三个人都想抢苹果,三个让可以同时拿苹果,但是不能抢同一个苹果,信号量就是保护你们不抢同一个苹果。

信号量结构体

信号量的初始化只有一种

第二个参数一般设置为0,第三个参数是sem量的初始值,类似于上面的盆子里有几个苹果,成功返回0。

申请信号量,也就是上面申请抢一个苹果,成功返回0.

释放信号量,相当于有人往盆里放苹果,成功返回0。

六,线程安全条件

什么样的线程有风险,什么样的线程是安全的呢?

1,常见的线程安全情况

只读不写

执行流里面的写操作都是原子性的

多个线程切换不存在二义性

2,常见的线程不安全情况

不保护多线程共享的变量

执行流的状态随着执行,被调用状态发生变化

返回指向静态变量的函数

调用线程不安全的函数

3,死锁

死锁是指各自不释放自己占有有资源,但因为有资源抢夺不到而都无法导致一种尴尬的场景。举个例子,想要打开一个宝箱需要两个要是,有两个人各自持有一把锁(线程各自持有一个锁),双方互不相让,导致谁也打不开宝箱,死锁和多个锁之间分配顺序的不同有很大关系。

死锁但是有四个必要的条件

1,不可剥夺性,线程占有资源互不相让,别人无法强行抢夺自己以有的资源

2,互斥条件 ,一个资源不能同时被多个人使用

3,请求和保持条件,一个执行流因请求资源而阻塞时,对已获得的资源保持不放

4,循环等待条件,形成了环路,造成了尴尬的场面,谁也无法好过。

如何避免死锁

破坏上面的四个形成的必要条件之一,死锁就不攻自破

加锁顺序一致,防止各自持有对方所需的资源

避免锁未释放,资源被锁死

资源一次性释放

银行家算法:模拟资源分配,如果产生了死锁就撤销任务不分配资源

死锁检测算法

拓展:C++里面的各种STL容器为了追求效率是没用加锁的,使用的时候要注意线程安全。

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

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

相关文章

碳微球是新型碳材料 在高科技领域应用价值极高

碳微球是新型碳材料 在高科技领域应用价值极高 碳微球是一种新型碳材料&#xff0c;由石墨片层在玻璃相的石墨结构间断分布而构成。   与碳纳米管、石墨烯等碳材料不同&#xff0c;碳微球具有独特的球形结构&#xff0c;这赋予了其高比表面、高堆积密度等特点及良好的导电性、…

【高阶数据结构(八)】跳表详解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:高阶数据结构专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多数据结构   &#x1f51d;&#x1f51d; 高阶数据结构 1. 前言2. 跳表的概…

python下用cartopy绘制地形晕染(shading)图

python可以利用rasterio&#xff0c;cartopy&#xff0c;matplotlib等库绘制地形晕染图。 1.获取高程数据 高程数据可以从GEBCO网站下载&#xff1a;&#xff08;https://www.gebco.net/data_and_products/gridded_bathymetry_data/&#xff09;。 选择raster&#xff08;栅…

浅谈一些AIGC赚钱赛道

前段时间&#xff0c;做过一期关于AIGC的分享。 ​缘起于近两年看到 DELL E 到 Stable Diffusion 多模态文本可控图像生成的大火&#xff0c;让AIGC概念涨了一大波流量。百度等一些头部大厂&#xff0c;以及关注元宇宙、web3.0领域的很多媒体和公司&#xff0c;都蹭上了这波热…

el-table动态配置显示表头

在实际工作中&#xff0c;会遇到动态配置e-table表头的情况&#xff0c;如下方法可以实现&#xff1a; // 要展示的列 column: [{prop: name, name: 名称 }, {prop: age, name: 年龄 }, {prop: sex, name: 性别 }, {prop: address, name: 地址 }, {prop: city, name: 城市 }]…

生活旅游数据恢复:全国违章查询

【步骤一&#xff1a;备份数据】 在开始数据恢复之前&#xff0c;首先要做的是备份现有的数据。虽然这一步不直接涉及到数据恢复&#xff0c;但万一在恢复过程中出现问题&#xff0c;您还可以回滚到备份&#xff0c;以避免数据丢失。 打开全国违章查询app。在主界面上找到并点…

量化投资分析平台 迅投 QMT(二)

量化投资分析平台 迅投 QMT [迅投 QMT](https://www.xuntou.net/?user_code7NYs7O)我目前在使用如何获取数据上代码历史帖子 迅投 QMT 我目前在使用 两个月前&#xff08;2024年4月&#xff09;迅投和CQF有一个互动的活动&#xff0c;进行了平台的一个网上路演&#xff0c;刚…

数据隐私重塑:Web3时代的隐私保护创新

随着数字化时代的不断深入&#xff0c;数据隐私保护已经成为了人们越来越关注的焦点之一。而在这个数字化时代的新篇章中&#xff0c;Web3技术作为下一代互联网的代表&#xff0c;正在为数据隐私保护带来全新的创新和可能性。本文将深入探讨数据隐私的重要性&#xff0c;Web3时…

WSDM 2023 推荐系统相关论文整理(二)

WSDM 2023的论文录用结果已出&#xff0c;推荐系统相关的论文方向包含序列推荐&#xff0c;点击率估计等领域&#xff0c;涵盖图学习&#xff0c;对比学习&#xff0c;因果推断&#xff0c;知识蒸馏等技术&#xff0c;累计包含近四十篇论文&#xff0c;下文列举了部分论文的标题…

STM32H750外设ADC之外部触发和注入管理

目录 概述 1 外部触发转换和触发极性 1.1 外部触发条件 1.2 忽略硬件触发条件 1.3 触发框图 1.4 常规通道的外部触发 1.5 注入通道的外部触发 2 注入通道管理 2.1 触发注入模式 2.2 自动注入模式 2.3 注入转换延迟 概述 本文主要介绍STM32H750外设ADC之外部触发和注…

Win10 TiKV单机单节点Docker部署测试

1. 环境 环境&#xff1a;Windows10、WSL2、Ubuntu20.04、Docker Desktop目标&#xff1a;单节点单机部署&#xff0c;测试用 2. 前置操作 docker pull pingcap/tikv:latest docker pull pingcap/pd:latestmkdir -p /mnt/tikv/pd mkdir -p /mnt/tikv/tikvip a 命令查看虚拟…

PROFINET转CANOPEN(WL-ABC3033)连接台达伺服驱动器ASDA-B3

在工业自动化领域这片广阔天地中&#xff0c;通信协议的转换犹如一道横亘在工程师们面前的难题。特别是在将众多采用不同通信协议的设备汇聚一堂&#xff0c;共同协作完成任务的场景中&#xff0c;如何确保数据如丝般顺滑地穿梭于各个节点之间&#xff0c;确保每台设备都能心领…

智慧社区信息化建设整体解决方案(PPT原件获取及软件各类建设方案)

智慧社区信息化系统建设要点可以归纳为以下几个方面&#xff1a; 一、社区基础设施建设 网络设施&#xff1a;建设高速网、城域网、校内网等网络&#xff0c;以满足社区信息传输和管理所需。信息终端设备&#xff1a;建设各种类型的智能终端设备&#xff0c;包括智能手机、智能…

【GD32F303红枫派使用手册】第八节 TIMER-RGB彩灯实验

8.1 实验内容 通过本实验主要学习以下内容&#xff1a; RGB彩灯控制原理 TIMER PWM输出原理 8.2 实验原理 本例程中使用的RGB彩灯采用共阳极驱动方式&#xff0c;使用三路PWM进行驱动&#xff0c;对应引脚输出低电平的时候对应RGB灯珠点亮&#xff0c;调节不同路的PWM占空…

FPGA新起点V1开发板(八-语法篇)——状态机

文章目录 一、两个状态机模型二、状态机设计&#xff08;四段论&#xff09;2.1 状态空间定义2.2 状态跳转&#xff08;时序逻辑&#xff09;2.3 下个状态判断&#xff08;组合逻辑&#xff09;2.4 各个状态下的动作2.5 三段式 一、两个状态机模型 二、状态机设计&#xff08;四…

用户投诉对旅行社复购率有什么影响?该如何分析投诉数据?

随着在线旅游市场的不断扩大&#xff0c;旅游平台的用户基数和交易量持续增长&#xff0c;用户投诉作为服务质量的反馈机制&#xff0c;其重要性日益凸显。用户投诉不仅反映了旅游服务中存在的问题&#xff0c;也是推动平台中的旅行社改进服务、提升用户体验的重要动力。然而&a…

接口自动化-预期值和实际值怎么写?

测试类当中 怎么做接口自动化&#xff0c;返回值校验&#xff0c;就是需要返回值的预期值和实际值进行对比 实际值如下 怎么拿到预期值$.msg?用正则表达式-提取值 建新的类-来编写用正则表达式拿到预期值 源码pattern 使用的compile的方法&#xff0c;传入的是字符串正则表…

短剧cps系统搭建开发,热门短剧推广分销系统。短剧分销是怎么操作的?

目录 前言&#xff1a; 二、短剧是怎么推广分销的&#xff1f; 二、 短剧分销系统有什么功能&#xff1f; 三、怎么搭建&#xff1f; 总结&#xff1a; 前言&#xff1a; 短剧分销项目目前的现状是多元化且充满活力的。随着短剧市场的快速发展和观众接受度的提高&#xff0…

大功率LED照明芯片OC6781输入5V~36V,PWM升压型LED恒流驱动器

概述 OC6781是一款高效率、高精度的升压型LED恒流驱动控制芯片。OC6781内置高精度误差放大器&#xff0c;振荡器&#xff0c;恒流驱动电路等&#xff0c;特别适合大功率、多个高亮度LED灯串恒流驱动。OC6781采用固定频率的PWM控制方式&#xff0c;工作频率可通过外部电阻进行设…

MySQL的组成与三种log

MySQL由几块组成 连接器分析器优化器执行器 MySQL的三大log blog 作用&#xff1a; 用于主从同步与数据恢复 记录内容&#xff1a; 已经完成的 DML(数据操作语句)&#xff0c;主要是用于数据备份 redolog<重试日志> 作用&#xff1a; 崩溃恢复&#xff0c;用于事…