线程控制.

线程已经成为调度的基本单位了,每一个线程都属于同一个地址空间中,所有的线程都属于同一个进程
换句话任何一个线程尝试调用geipid它应该是同一个pid
可是OS选择线程时,他怎么知道哪个线程是主线程?哪个是新线程?线程也有主次之分
每个线程都是一个调度的基本单元,所以每个线程都要有自己调度的id值啊?反正记着每一个线程也要有一个id

正是因为OS用进程内核数据结构模拟的线程,所以内核中有没有很明确的线程的概念呢?
没有的。它只有一个轻量级进程的概念
既然他只有轻量级进程的概念的话,注定了linux os不会给我们直接提供线程的系统调用,只会给我们提供轻量级进程的系统调用!
可是我们用户需要线程的接口!
linux程序员提供了pthread线程库 – 应用层 – 轻量级进程接口进行封装。 为用户提供直接线程的接口
这是一个第三方库,几乎所有的Linux平台,都是默认自带这个库的!
Linux中编写多线程代码需要使用第三方pthread库!!

那到底怎么使用这个库呢?
快速使用一下线程接口

创建
在这里插入图片描述
pthread_t 就是一个无符号长整型
在这里插入图片描述

返回值,成功返回0 ,如果失败的话用返回值的形式来告诉错误码是几,它没有使用errno
在这里插入图片描述


在这里插入图片描述

一旦调用pthread_create创建线程成功时,新线程转而执行threadRoutine该执行流,从上往下执行如果是死循环那线程永远执行,
如果结束了那新线程也就结束了
而主线程继续向后执行,主线程一开始就有了,main函数是他的入口函数
所以一个进程内会有两个执行流分别执行main函数后续代码,和新线程指向的新代码threadRoutine
因为main函数和指定函数threadRoutine 它在同一块代码被编译时一定使用代码部分不同的地址空间范围,代码区虽然是一整块,但不同函数用的不同的地址空间,注定了这两个线程执行时在代码资源上是分离的。就是说的代码资源分配

在这里插入图片描述
这是第三方库不属于c/c++语言,如果不指明用哪一个库,会出现链接错误
那么-I -L怎么不带上呢?
因为pthread库在系统里已经默认安装了,编译器能找到库的路径的,只是不知道要链接哪一个库而已
在这里插入图片描述
用ps axj查进程 会发现只有一个进程,这个进程里有两个执行流
在这里插入图片描述

我们可以用新命令
ps -aL 查看当前用户启动所有轻量级进程
在这里插入图片描述
LWP是什么呢?
Linux中CPU调度基本单位是线程,线程在Linux当中叫做轻量级进程,cpu调度时不仅仅只看pid
,更要看到每一个轻量级进程也要有自己对应的标识符,所以轻量级进程就有了LWP,叫做light weight process id
一个是6631 一个是6632
所以cpu调度执行流是按照LWP来进行调度的
所以CPU根本就不看PID,他看的是LWP,所以你以前是不是讲错了,以前不是按照进程为单位来调度的,每个进程都要有标识符吗?
仔细看上面的执行流它的PID 和LWP 是相同的,证明了:
1 . 上面线程叫做主线程,因为PID == LWP = = 6631 ,最先有的线程是它
2.下面那个线程 PID != LWP 说明他是被创建出来的
OS就能根据LWP和PID是否相等来决定你是否是主线程。

所以以前调度时我们讲错了吗?并没有
以前启动的每一个进程它调度时拿PID和拿LWP是一样的效果
单进程不就是一个进程内只有一个线程

LWP是调度的基本单位,OS调度时看LWP,但是他也能够确定哪一个是主线程,哪一个是新线程

发信号杀掉任意一个线程默认都会导致整个进程被杀掉
那这个信号算是发给线程的还是发给进程的?
我们认为他是发给进程的,因为每一个线程都是进程的执行分支,你发给线程就是发给进程的。

今天我们再写一个show函数,这个函数可以被多个执行流同时执行,那么show函数被重入了哦
在这里插入图片描述
在这里插入图片描述
今天再定义一个全局变量,所有线程都是共享的。
在这里插入图片描述
在这里插入图片描述

为什么都能看到,因为它们共享地址空间啊。
所以线程之间要通信很容易,如果定义一个大缓冲区,一个线程写一个线程读那不就俩通信了吗
所以线程中天然两者之间看到的资源共享,为通信方便提供了很好的技术准备
线程的共享性容易实现,他是先进性的表现

如果线程异常了导致进程收到浮点数错误,所以整个进程被干掉了
在这里插入图片描述


我现在挺好奇pthread_create形参的那个控制线程的tid,就想打印出来看看
在这里插入图片描述
你这个tid 也不是 LWP啊,那你这个tid是啥?
在这里插入图片描述
我们把tid的打法改为%p,真像个地址。
所以tid并不是直接是线程的LWP,因为LWP是OS层的概念,只要OS自己知道就行了,
作为用户不关心,我只关心tid,是给用户去使用的
在这里插入图片描述

等待
一个新线程一旦被创建出来了,是主线程先跑还是新线程先跑呢?
不确定,调度器说了算
谁应该最后退出呢?
肯定是主线程
为什么老是让他最后退出呢?
谁让你主线程创建了新线程,创建新线程本质就是在对线程做管理
所以你既然要管不把人家新线程管完算什么管理呢?
你不能自己先退了,让人家新线程怎么办。
目前所知,其实主线程要是退了一般代表进程退出了,所以新线程就跟着退
谁先运行不清楚,一定要保证主线程最后退

那我主线程就当甩手掌柜死循环干我自己的事情,为啥不让主线程退因为你得管我新线程
我新线程要是退了,你不管吗
所以新线程退出时也要被等待,如果你主线程不等我,也会造成类似子进程退出父进程不等待
的情况,造成类似于僵尸进程的问题
一句话
新线程被创建一般也要被等待,如果你不等默认会导致类似僵尸进程的问题
这个代码我们没办法验证,因为新线程一退查也查不到了,但确实存在这个问题

更重要的是,为什么要创建一个线程啊?我把新线程创建出来就是为了办事情的。
你把事情办的如何,我怎么知道?还是我不关心了?
所以新线程运行结果数据你是不是也得给我主线程

所以线程等待
1.防止新线程造成内存泄漏
2.如果需要你也可以获取新线程的退出结果

怎么等待呢?

pthread_join 等待一个终止的线程
在这里插入图片描述
pthread_t tid 传入你要等待哪个线程

返回值成功返回0,失败返回错误码
线程类函数所有出错码不用errno,统一用返回值返回,他不用全局的

新线程一旦把自己入口函数执行完了,默认线程就退出了
新线程退出不会影响主线程,
在这里插入图片描述

主线程等待的时候,默认会阻塞式等待新线程!
在这里插入图片描述
现在已经可以用pthread_join保证新线程不退,我主线程也不退,阻塞等待

现在的问题是主线程怎么知道新线程执行完毕的执行结果如何?
一个线程执行完毕的执行结果最终通过返回值让我们知道的。
现在的问题是,你的主线程怎么知道新线程的返回值呢?他们可是两个执行流

你是两个线程,一个主一个新
两个task_struct在内核中

新线程的返回值返回给用户层,主线程在用户层可以通过pthread_join来吧新线程的返回值拿到
不管是pthread_join库函数接口 还是新线程的返回值都属于pthread库的内部!
新线程执行完返回值直接写到pthread库里
你要获取库里面的void* ,用户自己需要定义一个void* 变量,取地址传给形参
join里面第二个形参二级指针void** ,在join里面这个形参解引用,再把库里面新线程返回值赋值给这个形参解引用,上层用户定义的void* 变量就拿到了新线程的返回结果。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
有点问题,一个执行流退出结果有三种呢,为什么这里怎么不管线程出异常了?
线程也有可能出异常啊,为什么你join只有退出码不考虑异常呢?

答:它做不到,一旦线程出异常,主线程也就跟这遭殃了join还返回啥呢。
异常问题是进程考虑的。
你线程hold不住,你只考虑退出码这种情况就行

终止

如果在任何一个线程中直接exit( ) ,会直接终止整个进程
exit是用来终止进程的,不能用来终止线程。

主线程要是退了,整个进程也跟着退,你别说线程还没执行完,线程也要退

1、线程函数中直接 进行return ,就代表线程退出
2、pthread_exit( )
谁调用就终止谁,参数就是线程函数返回值,return退出码
在这里插入图片描述

3、线程取消pthread_cancel 不常见
在这里插入图片描述

前提是线程真的已经创建出来了

形参是要取消哪一个进程 tid

如果一个线程本身是被取消的,该线程退出结果是PTHREAD_CANCEL是宏-1
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


根据目前知识,线程的大部分资源都是共享的
正文代码其实也是共享的只不过每个线程一人一块代码,单独写个公共方法函数被两个线程都可以读到,函数是随时可以被重入

全局变量也是可以多线程访问的

共享区两个线程都在用cout printf,说明共享区也是被所有线程共享的

栈一定是被所有线程私有的

命令行环境变量今天不考虑

关键在于堆区,重谈线程入口函数参数和返回值

线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!
不要狭隘的认为参数传个字符串,返回值返回个整数
在这里插入图片描述
我们现在整个代码都是在堆上new的,发现代码互相交叉式的
在主线程New了一个对象传递给了新线程,在新线程new了一个对象传递给了主线程
说明什么?
说明堆空间也是被线程共享的
这不会出问题吗?一般不会
因为堆空间指针谁拿着指针,谁就访问堆空间


目前我们的原生线程pthread库,属于原生线程库

c++11语言本身也已经支持多线程了 VS 原生线程库 他们两个什么关系?
在这里插入图片描述

别和我说什么C++11支持什么多线程,它的底层实现就是封装原生线程库
编译时如果你不加 -lpthread 就找不到这个库
在这里插入图片描述


重新理解一下什么叫做原生线程,你刚刚看了pthread_create会产生一个线程ID
这个ID可不是内核级别的LWP那个数字,他们俩不一样
现在我想知道这个线程ID是什么?

另外你还说线程都有自己独立栈结果,你怎么证明?

在这里插入图片描述

clone和fork底层原理类似,创建子进程
clone专门用来创建轻量级进程的,我们不用这个接口,看一下复杂的接口参数就知道了

在这里插入图片描述

现在的问题是clone我们用不了,有些接口系统不让用,这个接口就被pthread线程库封装了
给我们提供了create , join…这样的接口
上层用户要有线程的概念,也就是线程库
clone允许用户在应用层传入1个回调函数,和1个用户空间来代表轻量级进程在运行过程中:
1.它要执行的代码
2.它在运行过程中形成的临时变量

线程库要封装clone,每一个线程都要给它提供执行方法,线程库内部还要开辟空间
把方法交给clone里面的回调函数,栈也交给它
方法最终暴露出去就是你自己创建线程线程时pthread create 传的回调函数,栈结构你是没管的

也就是说线程的概念是库给我们维护的。
问题:
当你自己执行多线程代码时用原生线程库时你这个库要不要加载到内存中,加载到哪里??
原生线程库可是一个动态库啊
在这里插入图片描述
答:
在这里插入图片描述

要加载到内存里,别跟我扯什么线程,我就是一个进程
执行时这个库一定被加载到内存中,经过页表把pthread库映射到了共享区里面
不要觉得共享区只有c/c++库
对应pthread库里面,每一个创建好的线程,在库里面就要给我们开辟出一段空间用来充当新线程的栈。
所以线程库要加载到内存,未来所有讨论都是基于内存的!

所以我的线程的ID是多少,我的栈是多大,我线程回调方法地址是什么,我线程时间片是多少?
所有这些字段请问OS关不关心,知不知道?
不知道,因为它没有线程概念
但是这些所有属性,叫做线程的属性
线程的概念是库给我们维护的这句话该怎么理解呢?
线程概念既然是库给我们维护,意味着线程库中要维护线程,主要维护线程的概念,
不用维护线程的执行流!
意思就是说,线程底层就是轻量级进程执行流tast_struct,但是线程相关很多用户关心 的属性
必须由库来维护

线程库里面同时会存在多个被创建的线程很正常,
线程库注定要维护多个线程属性集合。线程库要不要管理这些线程呢??
要,先描述,在组织!
所以每创建一个线程在线程库中就要创建线程库级别的线程控制块
,它包含了线程很多属性,对上为用户提供诸如这个线程回调函数在哪里,独立栈在哪里,
线程的id是多少,
更重要的是线程的lwp指向底层哪个执行流task_struct,用户未来访问线程时,它只要找到线程控制块执行流task_struct自然而然就会由OS底层自动调度它就执行你上层的代码了
你想获取线程的属性你就通过线程控制块获取吧

所以线程是由用户层维护的,他是在OS之上的,称为用户级线程!
用户级线程什么鬼呢?说白了就是由用户把线程的结构体维护起来。

所以再来看这张图
在这里插入图片描述

这是某一个PCB的进程地址空间,这是你加载到内存的pthread.so库
库已经加载到内存了,然后被映射到进程地址空间共享区的
你这个进程一把线程,另一个进程也可以一把线程,不要紧,你们都映射同一个库
随着线程创建越来越多,每一个线程在创建时都在库里创建一个线程tcb,
tcb包含了线程很多属性,每个线程都有这个结构,所以在这个库里面把所有tcb按照数组方式给我们维护好,先描述在组织就有了
未来访问线程。比如等待线程,获取线程退出结果,退出结果就是写在它的局部存储里,
你要形成变量时要对数据做压栈,你就用线程栈。
未来你有十个线程,就有10个tcb
为了快速找到每一个tcb在共享库里,所以把一个tcb在内存中起始地址叫做线程的tid!!
为什么要用这个起始地址作为tid呢?
1.它存的是地址
2.它在用户空间
3.是虚拟地址可以直接访问
所以线程你想获取他的属性直接拿着tid,库函数他就直接找到属性了


下面是时候谈 线程栈了
在这里插入图片描述

每一个线程运行时一定要有自己独立的栈结构
因为每一个线程都会有自己的调用链
注定了每个线程必须有自己独立调用链所对应的栈帧结构

其中 主线程直接使用地址空间中的栈即可

我们剩下的新线程,首先在共享区线程库里面为新线程创建tcb,
起始地址作为线程tid, 线程控制块包含默认大小一段空间叫做线程栈,
然后要在内核中创建执行流pcb,它就在库中调clone,
把对应线程执行方法和线程栈传递给clone,
所以clone执行流调用时中间形成的临时数据都会压入到这个tcb里面的线程栈

换句话说,所有对应非主线程它的栈都在库中维护,即共享区维护
除了主线程,所有其他线程的独立栈,都在共享区具体来讲是在pthread库中,tid指向的用户tcb中!

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

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

相关文章

C语言-字符串变量

字符串变量 char* s “Hello, world!”; s是一个指针,初始化为指向一个字符串常量 由于这个常量所在的地方,所以实际上s是const char* s,但是由于历史的原因,编译器接受不带const的写法但是试图对s所指的字符串做写…

CAD画图-模型和布局区别,视图命令MV使用(用于局部放大显示)

模型和布局的图像区别 模型的图像: 是我们常编辑的cad文件,我们可以对里面内容进行编辑和测量等操作 布局的图像:为了可以更好的看到每个部件的相对位置,但对于里面的点位的标注就不行了,但可以对图像中的某些部位进行…

软件项目功能测试框架

测试用例的编写需要按照一定的思路进行,而不是想到哪写到哪,一般测试机制成熟的公司都会有公司自己自定义的测试用例模板,以及一整套的测试流程关注点,当然我们自己在测试生涯中也应当积累一套自己的测试框架,所有功能…

二极管:TVS瞬态抑制二极管

一、什么是TVS二极管 TVS(Transient Voltage Suppressors),即瞬态电压抑制器,又称雪崩击穿二极管。 TVS二极管的符号如下图所示 什么是雪崩击穿 雪崩击穿是有必要了解一下的,不然后面还有齐纳击穿,搞不…

第二十一章 网络通信

21.1 网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。 局域网与互联网 服务器是指提供信息的计算机或程序,客户机是指请求信息的计算机或程序。网络用于连接服务器与客户机,实现两者间的相互通信。 网络协议 网络协议规定了计算…

P4715 【深基16.例1】淘汰赛-仅思路

首先从题干要求入手,我们可以了解到题目要求是二进一,不难想到这是二叉树的题 再来,从题干可以知道,我们所采用的结构体除了需要有树的两个左右节点指针外,还需要两个变量用来储存“能力值”和“编号” 在这道题中&am…

竞赛选题 题目:基于深度学习的图像风格迁移 - [ 卷积神经网络 机器视觉 ]

文章目录 0 简介1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 简介 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习卷积神经网络的花卉识别 该项目较为新颖,适合作为竞赛课题方向&#xff0c…

CoreDNS实战(三)-CoreDNS+ETCD实现DNS负载均衡

1 概述 DNS负载均衡简单来说就是通过一个域名绑定多个IP地址,当客户端访问域名时,DNS服务器将轮询返回其中一个IP,实现客户端分流的作用。 在K8s环境中CoreDNS作为容器服务的DNS服务器,那么就可以通过CoreDNS来实现DNS负载均衡&a…

【Linux】基础IO--重定向理解Linux下一切皆文件缓冲区

文章目录 一、重定向1.什么是重定向2.dup2 系统调用3.理解输入重定向、输出重定向和追加重定向4.简易shell完整实现 二、理解linux下一切皆文件三、缓冲区1.为什么要有缓冲区2.缓冲区的刷新策略3.缓冲区的位置4.实现一个简易的C语言缓冲区5.内核缓冲区 一、重定向 1.什么是重定…

Java---类的继承

文章目录 1. 理解继承2. 继承概述3. 代码块理解4. 继承的好处与弊端5. 继承中变量的访问特点6. super关键字7. 继承中构造方法访问特点8. 继承中成员方法访问特点9. 方法重写10. 方法重写注意事项11. Java继承注意事项 1. 理解继承 2. 继承概述 1. 继承是面向对象的三大特征之一…

客观题测试-第6章图

第1关:图-客观题测试 (一) 1、无向图中一个顶点的度是指图中()。 A、通过该顶点的简单路径数 B、与该顶点相邻接的顶点数 C、与该顶点连通的顶点数 D、通过该顶点的回路数 2、以下说法正确的是(&…

spring boot 2 升级到 spring boot 3 后文件上传失败

背景 项目需要,要求升级 spring boot 2.7 到 spring boot 3.2,升级过程中发现很多不兼容问题,下面说明文件上传失败的解决方案。 问题 spring boot 2 中不需要额外的配置,直接在 Controller 中配置 MultipartFile 接收页面传的…

Linix服务器添加dns解析

Linix开通互联网域名地址出现,如下错误: 需要访问的服务器上添加dns解析 vim /etc/sysconfig/network-scripts/ifcfg-ens192 添加如下配置: DNS1202.96.134.13 重启网卡: systemctl restart network 注意如果是docker服务部署…

利用github copilot完成代码,利用正则化完成字符串中信息查找

利用正则化完成字符串中的字符拆解。 下面的代码是实现在“计算机组成原理-计科2101-123456-小明同学.docx”中提取出班级(grade),学号(id),姓名(name)。以下的代码都是github copi…

vivado时序方法检查2

TIMING-4 &#xff1a; 时钟树上的基准时钟重新定义无效 时钟树上的时钟重新定义无效。基准时钟 <clock_name> 是在时钟 <clock_name> 下游定义的 &#xff0c; 并覆盖其插入延迟和/ 或波形定义。 描述 基准时钟必须在时钟树的源时钟上定义。例如 &#xff0…

企业电子招投标系统源码之电子招投标系统建设的重点和未来趋势

功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查看所…

美国DMF号查询方法及网址

美国的DMF制度于1989年开始实施&#xff0c;并一直延续至今。美国DMF制度是首创&#xff0c;欧洲以及其他后续的加拿大、澳大利亚等&#xff0c;都是在仿美国的DMF制度。下面笔者就带大家来了解什么是美国DMF&#xff1f;如何快速查询美国DMF注册备案信息&#xff1f; 关于DMF的…

Spring-AOP

目录 一、引入AOP 二、核心AOP概念和术语 三、切点表达式 四、Spring实现AOP &#xff08;一&#xff09;AspectJ的支持 1 、基于注解开发 1.1 引入依赖 1.2 实现目标类 1.3 定义切面类&#xff08;日志管理&#xff09; 1.4 将目标类和切面类纳入Spring容器 1.5 开…

JFrog Artifactory—高性能软件制品管理仓库

产品概述 JFrog Artifactory是一个可扩展的通用二进制存储库管理器&#xff0c;可在整个应用程序开发和交付过程中自动管理工件和依赖项。JFrog Artifactory支持大多数开发语言&#xff0c;是整个DevOps流水线中大多数软件包、容器映像和Helm图表的单一数据源。Artifactory对元…

四.多表查询

多表查询 1.一个案例引发的多表连接1.1案例说明1.2 笛卡尔积&#xff08;或交叉连接&#xff09;的理解1.3案例分析与问题解决 2.多表查询分类讲解分类1&#xff1a;等值连接vs非等值连接分类2&#xff1a;自连接vs非自连接分类3&#xff1a;内连接vs外连接 3.SQL99语法实现多表…