Linux-多线程

news2024/9/25 19:24:47

 线程的概念

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
  • 进程是承担资源分配的基本实体,线程是cpu调度的基本单位

        在操作系统的学习中,我们可能在课本上看到过进程是被PCB描述的,而线程是被TCB描述的,在Linux中对于线程的设计有所不同,并没有设计出struct tcb的结构体,Linux实现线程是通过复用原有结构PCB,而windows操作系统就依据操作系统书籍上的规定设计出了专门管理线程的TCB结构体,相比于windows的做法Linux是更具有优势的,因为线程是在进程内部运行的,它的结构和数据和进程是比较相似的,采用复用PCB的方式,可以省去为线程创建初始化TCB结构体、构建地址空间等工作,只需要创建一个PCB然后与进程复用同一份资源就可以了,可以节省空间,并且如果将线程单独设计出来,还要新设计出线程的调度算法,成本比较高,此外,Windows对于线程的设计更为复杂,程序出问题后的调试工作相比于Linux也会更加困难。

问题:已经有多进程了为什么还要有多线程?

【解释】:

        在启动方面,创建一个进程需要构建进程PCB,地址空间、页表并将与物理内存构建映射关系,再将内存的数据导入,而创建一个线程只需要创建一个PCB,将进程的资源导入即可,因为其与进程共用一个地址空间和页表,内存的映射关系也是一样的,相比之下创建一个线程的代价是很小的

        在运行方面,调度线程也是比调度进程速成本低的。在CPU内部存在一个cache缓冲区,假设进程A要访问第五行代码,根据局部性原理,OS可能认为下一步可能会访问第六行、第七行代码,OS就会直接将周围的50行代码,或者一个代码块预先加载到cache中,cache中的数据称为热数据,如果下一次访问数据会先在cache中查找,如果命中的话就可以直接使用了,如果没有命中则重新加载数据,当进程A访问完,轮到进程B调度了,此时cache中的热数据进程B一点都用不到,还需要重新从内存中加载数据到cache中,效率比较低。而由于线程执行的是进程中的代码,当一个进程的线程进行调度时,cache中的代码不同的线程可能都用的到,就不需要重新加载了。

        在删除方面,删除一个进程需要删除进程PCB、地址空间、页表等,而删除一个线程只需要删除对应的PCB即可。综上所述,多线程相比于多进程存在许多优点

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现,充分利用硬件资源
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

 线程的缺点

1. 性能损失

        一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。例如一个单核的CPU我们硬要将一个计算分给10个线程进行,此时的效率可能没有让一个线程直接计算的效率高,因为此时有线程调度切换的成本,所以线程并不是创建越多越好

2. 缺乏访问控制

      在一个多线程的程序中,由于线程是共用进程的地址空间的,大多数数据其实是被共享的,如果一个线程无意改变了另一个线程的数据,可能会造成不良影响

3. 健壮性差

        单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该 进程内的所有线程也就随即退出

线程的用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是 多线程运行的一种表现)

进程与线程对比

  1. 进程是资源分配的基本单位
  2. 线程是调度的基本单位
  3. 线程共享进程数据,但也拥有自己的一部分数据:
  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级

其中比较重要的是寄存器和栈

线程拥有自己独立的寄存器,主要是出于以下几个方面的考虑:

  1. 独立计算:每个线程在执行时都需要一个独立的上下文环境,以便进行独立的计算。拥有自己的寄存器可以确保线程在执行过程中不会受到其他线程的影响,从而保持计算的准确性和独立性。
  2. 避免冲突:在多线程环境中,如果多个线程共享同一个寄存器,那么它们之间的操作可能会相互干扰,导致数据错乱或执行错误。拥有独立的寄存器可以避免这种冲突,确保每个线程都能在自己的环境中安全地执行。
  3. 减少上下文切换开销:当CPU在多个线程之间切换时,需要保存和恢复每个线程的上下文环境。如果线程拥有独立的寄存器,那么在切换时只需要保存和恢复这些寄存器的值,而不需要处理共享资源可能带来的复杂同步问题,从而减少了上下文切换的开销。
  4. 快速恢复执行状态:由于每个线程都拥有自己的寄存器,因此在被调度执行时,可以迅速地从寄存器中恢复执行状态,而不需要重新加载或计算大量的数据,这有助于提高线程的执行效率和响应速度。
  5. 并发控制:在多线程并发执行时,每个线程都需要独立地控制自己的执行流程和状态。拥有独立的寄存器可以使得线程在并发执行时能够更好地控制自己的执行流程,避免因为共享资源而导致的并发控制问题。
  6. 并行加速:在并行处理中,多个线程可以同时执行不同的任务。如果每个线程都拥有自己的寄存器,那么它们就可以并行地处理数据,从而提高整个系统的处理能力和加速比。

线程也要拥有自己独立的栈,因为如果多进程公用进程地址空间中的栈的话,线程之间进行入栈出栈操作可能会互相造成影响


进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

进程和线程的关系如下图:

进程控制

创建线程

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t
*attr, void *(*start_routine)(void*), void *arg);
参数
thread:返回线程 ID
attr:设置线程的属性,attr 为 NULL 表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回 0;失败返回错误码

错误检查:

  • 传统的一些函数是,成功返回 0,失败返回-1,并且对全局变量 errno 赋值以 指示错误。 
  • pthreads 函数出错时不会设置全局变量 errno(而大部分其他 POSIX 函数会这 样做)。而是将错误代码通过返回值返回
  • pthreads 同样也提供了线程内的 errno 变量,以支持其它使用 errno 的代码。 对于 pthreads 函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程 内的 errno 变量的开销更小

线程 ID 及进程地址空间布局

  • pthread_ create 函数会产生一个线程 ID,存放在第一个参数指向的地址中。 该线程 ID 和前面说的线程 ID不是一回事。
  • 前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程,是操作系统 调度器的最小单位,所以需要一个数值来唯一表示该线程。
  •  pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程 ID,属于 NPTL 线程库的范畴。线程库的后续操作,就是根据该线程 ID 来操作线程的。
  •  线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID:
pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于 Linux 目前实现的 NPTL 实现而 言,pthread_t 类型的线程 ID,本质就是一个进程地址空间上的一个地址。

由于我们使用的线程接口都是在线程库中维护的,所以我们想创建一个线程的前提是让线程库加载到内存并映射到地址空间,当我们创建一个线程时,pthread库就会在其内部创建一个结构用来管理这个线程,就像我们使用的文件操作,调用fopen函数会返回一个FILE*的结构体,而FILE结构体就是维护在c标准库中的,而tid就是线程库维护的,用于标识每个线程的唯一性,tid是进程级的内核看不到,是用户级的用来让用户操作。

我们通过ps -al可以查看当前进程的所有线程,其中的LWP(轻量级进程)也是用来唯一标识线程的,Linux下的tid和LWP在本质上是一致的,都是用来唯一标识一个线程。tid是系统调用层面使用的术语,而LWP则是从内核视角看到的线程实现方式。在用户程序中,我们可能更习惯于使用tid来标识线程;而在查看系统线程信息时,LWP则是一个常见的显示方式。两者之间的关系可以理解为内核态与用户态对线程概念的不同表述方式。

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit,主线程退出该进程的所有线程都会退出
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
pthread_exit函数
功能:线程终止
原型void pthread_exit(void* value_ptr);
参数value_ptr:value_ptr不要指向一个局部变量,用于后续接收从线程函数的返回值
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数
功能:取消一个执行中的线程
原型int pthread_cancel(pthread_t thread);
参数thread:线程 ID
返回值:成功返回 0;失败返回错误码

线程等待

  • 已经退出的线程(执行完函数),其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
  • 如果不进行线程等待会造成类似于僵尸进程的后果
功能:等待线程结束
原型:int pthread_join(pthread_t thread, void **value_ptr);
参数: thread:线程 ID 
      value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回 0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。在函数内部调用pthread_exit()和return的效果是一样的 
  3. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED,在标准输出中打印出来为-1
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

分离线程

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 一个线程在join其他线程时会被阻塞等待,不能做自己的事情,如果不关心线程的返回值,join是一种负担,这个时候我们可以告诉系统,当线程退出时,自动释放线程资源

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

int pthread_detach(pthread_t thread)

int pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的,当一个线程被分离后还被join,此时不会阻塞等待线程终止,join会直接出错返回

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

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

相关文章

IO之反序列化漏洞

hutool之XmlUtil反序列化漏洞 同样存在漏洞的方法还有IoUtil.readObject方法,存在反序列化漏洞,这些方法的漏洞在JDK中本身就存在,而且JDK的做法是要求用户自行检查内容,作为工具类,这块没法解决。hutool在新版本中把这…

800 元打造家庭版 SOC 安全运营中心

今天,我们开始一系列新的文章,将从独特而全面的角度探索网络安全世界,结合安全双方:红队和蓝队。 这种方法通常称为“紫队”,集成了进攻和防御技术,以提供对威胁和安全解决方案的全面了解。 在本系列的第一篇文章中,我们将指导您完成以 100 欧元约800元左右的预算创建…

电竞玩家的云端盛宴!四大云电脑平台:ToDesk、顺网云、青椒云、极云普惠云实测大比拼

本文目录 一、云电脑概念及市场需求二、云电竞性能测试2.1 ToDesk云电脑2.2 顺网云2.3 青椒云2.4 极云普惠云电脑 三、四大云电脑平台综合配置对比3.1 CPU处理器3.2 GPU显卡3.3 内存 四、总结 一、云电脑概念及市场需求 在数字化时代的推动下,云计算技术日益成熟&a…

【VUE基础】VUE3第九节—Pinia使用

Pinia使用 Pinia简介安装Pinia存储数据和读取数据State读取数据重置 state修改state值storeToRefs监听state Getter读取数据 Action Pinia简介 Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。 安装Pinia yarn add pinia # 或者使用 npm npm inst…

24年,计算机仍然是最热门的专业?!

大家好,我是程序员鱼皮。最近很多高考完的朋友开始进入了填志愿选专业的时期。出于好奇,我也在网上了解了一下今年的热门专业和就业情况,结果并没有出乎我的意料,对于很多省份,计算机科学与技术依然是最热门的专业&…

视频汇聚平台EasyCVR设备录像回看请求播放时间和实际时间对不上,是何原因?

安防监控EasyCVR视频汇聚平台可提供多协议(RTSP/RTMP/国标GB28181/GAT1400/海康Ehome/大华/海康/宇视等SDK)的设备接入、音视频采集、视频转码、处理、分发等服务,系统具备实时监控、云端录像、回看、告警、平台级联以及多视频流格式分发等视…

spring的bean注册

bean注册 第三方jar包的类想添加到ioc中,加不了Component该怎么办呢。 可以使用Bean和Import引入jar包,可以使用maven安装到本地仓库。 修改bean的名字:Bean("aaa")使用ioc的已经存在的bean对象,如Country:p…

HTML实现图片查看与隐藏

你好呀,我是小邹。 在网页设计中,提供一个直观且用户友好的图片查看功能是提升用户体验的重要一环。本文将详细介绍如何使用HTML、CSS和JavaScript来实现图片的查看与隐藏功能。通过本教程,你将学会如何让页面上的图片在点击时放大显示&…

产线级MES系统在装配行业的具体应用

在装配行业中,产线级MES系统能够帮助企业优化生产流程,提高产品质量,增强生产效率。以下是产线级MES系统在装配行业中的一些具体应用场景。 了解慧都产线级MES系统>> 产线级MES系统应用场景 1. 生产过程监控与管理 MES系统可以实时…

基石Apollo国际化整合及配置上线规范治理

背景 随着公司站点的增多和国际化业务的发展,Apollo配置平台也呈现出多国家、多环境部署、各环境配置孤立操作、配置上下线无规范流程的情况。基于当前的情况,主要的痛点问题如下: 国际化apollo未与主营apollo统一, 导致国际化Apollo生产配…

react umi把将file文件数据转成二进制流数据格式

后端要求文件上传传递二进制文件??? 参考 umi-request上传FormData类型问题_umi-request formdata-CSDN博客 import request from /utils/request; // 批量下发(此接口使用from表单接收) export async function issuance_audit_create(param…

第33讲:K8S集群StorageClass使用Ceph CSI供应商与Cephfs文件系统集成

文章目录 1.Ceph CSI供应商简介2.创建Cephfs文件系统为StorageCLass提供底层存储端2.1.创建Cephfs文件系统2.2.在Cephfs文件系统中为Storageclass创建子目录2.3.在Cephfs文件系统中创建一个子卷 3.在K8S集群中部署Cephfs-CSI供应商客户端3.1.下载Cephfs-CSI客户端的资源编排文件…

计算机网络——网络层(概念及IP地址)

网络层概念 网络层向上层提供的两种服务 在计算机网络领域,网络层应该向运输层提供怎样的服务(“面向连接”还是“无连接”)曾引起了长期的争论。 争论焦点的实质就是:在计算机通信中,可靠交付应当由谁来负责?是网络还是端系统&#xff1f…

rfid资产管理系统解决方案 rfid固定资产管理系统建设方案

在现代化的仓库储备中,仅仅完成对货物进出的简单批次处理已经不再足够,对库内货品的种类、数量、生产属性、垛位等信息的清晰记录变得至关重要。然而,传统的资产管理方式如条形码在长期使用中逐渐暴露出不耐脏、数据存储量小、读取间隔短、不…

js吸顶导航

吸顶导航 当我们浏览页面篇幅较大,浏览过半的时候想回到导航位置,只能通过往回滚动或通过”回到顶部”重新滚动到导航位置,这样的操作显得繁琐与不便。于是便有了吸顶式导航的交互方式,吸顶条导航最大的好处是将最常用或者设计者最…

WAIC | 穿越千年!华院计算AIGC技术实现刘徽、祖冲之和毕达哥拉斯跨时空对话

祖冲之利用刘徽的割圆术,将圆周率π的近似计算精确到小数点后七位,这不仅是数学史上的一项重要突破,也是对无理数逼近问题的早期探索。在现代人工智能中同样能观察到数值逼近的思想,例如模型的训练通常依赖随机优化算法等数值方法…

2.贪心算法.基础

2.贪心算法.基础 基础知识题目1.分发饼干2.摆动序列3.最大子序和4.买股票的最佳时机24.2.买股票的最佳时机5.跳跃游戏5.1.跳跃游戏26.K次取反后最大化的数组和7.加油站8.分发糖果 基础知识 什么是贪心? 贪心的本质是选择每一阶段的局部最优,从而达到全局最优。 贪…

Studying-代码随想录训练营day33| 动态规划理论基础、509.斐波那契函数、70.爬楼梯、746.使用最小花费爬楼梯

第33天,动态规划开始,新的算法💪(ง •_•)ง,编程语言:C 目录 动态规划理论基础 动态规划的解题步骤 动态规划包含的问题 动态规划如何debug 509.斐波那契函数 70.爬楼梯 746.使用最小花费爬楼梯 总结 动态…

MSPM0G3507——时钟配置(与32关系)

先将32端时钟配置分为1,2,3如图 1是PSC左边未经分频的时钟源(HZ) 2是经过PSC分频的时钟信号(HZ) 3是最终的输出信号(HZ) 3输出的是一个定时器周期的HZ,可以转换成时间 …

哨兵系统:一套实时灵活可配置化的业务指标监控系统

简介: 在KOO分期的线下业务中,需要对很多关键业务指标进行实时监控,并需要根据一定的数据格式,通过企微机器人发往对应的企微群,因此KOO分期技术团队在KOO业务指标库之上,搭建了一套KOO分期业务指标监控系统&#xff…