Linux:线程控制

目录

线程的相关知识

线程创建

pthread_create

线程关于进程内部资源问题

线程等待

pthread_join

线程退出

pthread_cancel

线程id的理解

pthread_self

线程分离

pthread_detach


线程的相关知识

首先线程是在进程内部执行的是OS调度的基本单位

线程的理解:之所以说线程是在进程内部执行的,是OS调度的基本单位,是因为线程是在进程的地址空间内运行,并且CPU其实是不关心执行流是进程还是线程,只关心PCB

而Linux中,一个进程有一个PCB结构体,即task_struct,以这个task_struct为父亲,不重新创建新的地址空间、页表,而是只创建新的task_struct,与进程的task_struct共用地址空间(mm_struct)、页表,并且通过一定的技术手段,将当前进程的资源,以一定的方式划分给不同的task_struct,上述这样的每一个task_struct就称之为线程


在前面我们学习过,用户视角中:进程 = 内核数据结构 + 该进程对应的代码和数据

并且之前学习进程时,进程的内核数据结构只有一个PCB结构体,今天引入了线程,知道了一个进程的内核数据结构可以有多个PCB结构体

只有一个PCB结构体,称之为内部只有一个执行流,而若有多个PCB结构体,说明进程内部具有多个执行流,其中一个task_struct就代表进程内部的一个执行流


今天站在内核视角:进程是承担分配系统资源的基本实体

因为系统分配的资源都是刚开始的进程向系统索要的,而下面的线程都只是用上面进程所申请的资源,所以内核角度说进程是承担分配系统资源的基本实体

CPU并不关心进程与线程的概念,只关注task_struct

在Linux下,PCB <= 其他OS内的PCB的,因为Linux中一个PCB有可能只会分到一部分资源(因为有多线程存在,可能有多个PCB),也有可能只有一个PCB,能够使用全部的资源,所以Linux中的PCB <= 其他OS内的PCB

因此Linux下的进程,统称为轻量级进程

Linux没有真正意义上的线程结构,因为没有为线程专门创建一个数据结构,Linux是用进程PCB模拟的线程的

所以今后在Linux中不严格区分进程和线程,统一叫做轻量级进程,进程内部只有一个执行流的话,就对应到别的OS中的单进程程序;若进程内部包含多个PCB,就对应到别的OS中的多线程

因此Linux不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口

Linux为了照顾用户的使用,因为用户只清楚进程和线程,也并不知道什么轻量级进程,因此在用户层实现了一套用户层多线程方案,以库的方式提供给用户进行使用,pthread线程库,即原生线程库


线程创建

pthread_create

创建线程的函数是pthread_create,使用man查看:

需要包含头文件pthread.h

函数参数:

第一个参数:线程ID,pthread_t是无符号长整型的

第二个参数:线程属性(默认nullptr即可,不用管)

第三个参数:是一个返回值为void*,参数为void*的函数指针,表示这个线程要执行进程的一部分代码时的入口地址

第四个参数:就是传递给上面的函数指针的参数,若pthread_create函数创建成功,就会把这个参数传递给函数指针,是一个回调的过程

返回值:

成功返回0,失败返回错误码

下面代码进行验证上述结论:

makefile:(Makefile中,线程库编译时必须要加-lpthread,否则会报错)

mythread.cc:

在新线程创建成功后,pthread_create中的第四个参数就会传给threadRun函数的参数args

mythread.cc代码中,main函数中在for循环中,创建了5个新线程,for循环下面是主线程,新线程和主线程分别打印各自的pid,观察pid是否相同,就可以判断出线程是否在进程里

下面首先make编译:

生成了可执行程序mythread,通过ldd mythread查看,发现使用了pthread库:

运行结果如下:

可以看到,主线程和新线程的pid都为26435,所以可以证明线程在进程内部运行

下面执行ps axj | head -1 && ps -axj | grep mythread:

可以观察到只能看到一个进程,pid为26435,但是右边却有6个执行流

下面使用ps -aL可以查看轻量级进程:

可以观察到确实有6个执行流,且pid都为26435 ,说明这六个执行流属于同一个进程

而上图中PID右边那一列LWP,是属于轻量级进程对应的pid,而第一行的PID与LWP相等,所以第一行的执行流叫做主线程,而下面的五个执行流叫做新线程

所以CPU调度线程时,看的其实是LWP,因为PID是1对n的关系,而LWP是一一对应的

而接下来,如果我们kill -9 26435,观察结果:

通过观察结果,杀死PID为26435的进程,再观察轻量级进程时,发现都被终止了,原因是:当前所有线程都是在进程内部创建的,而kill -9把进程终止了,就相当于该进程的资源要被回收,所以这些线程所需要的代码和数据也就会被回收,因此都被终止了


线程关于进程内部资源问题

线程大部分资源都是共享的,但是也有独自占用的资源

线程独自占用的资源:寄存器(即线程的上下文)、栈、错误码、信号屏蔽字、调度优先级

前两种最重要


线程切换的成本低,理由如下:

第一:线程切换时所用的地址空间、页表等不需要切换,而如果CPU调度时调度的PCB是另一个进程的PCB,那么在调度时就需要整体把CPU内部相关的上下文、临时数据、页表、地址空间等都需要切换

第二:CPU内部是有L1~L3cache(缓存),对内存的数据和代码,根据局部性原理(一条指令附近的代码,有较大的概率被使用),预读到CPU内部
           如果进程切换,原进程的cache就会立即失效,新进程只能重新缓存,所以进程切换的成本更高


线程等待

在上面我们kill -9 [进程pid]后,进程及相关的线程都被终止了,那么如果新线程出现异常呢,下面进行相关验证:

运行结果为:

发现发生了8号错误,并且进程也退出了

所以得到下面结论:

①线程谁先运行与调度器相关

②线程一旦异常,都可能导致进程整体退出

线程在创建并执行的时候,也是需要进行等待的,如果主线程不等待,就会引起类似进程的僵尸进程问题,导致内存泄漏问题

所以主线程就需要等待新线程,接口是pthread_join

pthread_join

使用man查看:

需要包含头文件pthread.h

函数参数:

第一个参数:线程id

第二个参数:开始先设为nullptr

函数返回值:

成功返回0,失败返回错误码

下面使用pthread_join函数进行线程等待:

运行结果为:

可以看到,新线程先退出,主线程等待成功后,资源回收后也退出

通过执行ps -aL | head -1 && ps -aL | grep mythread,也可以观察到线程的情况:

在主线程等待新线程时,可以看到两个线程都在运行,而新线程退出后,主线程等待成功也随之退出,此时再观察左边窗口,就没有正在运行的线程了

这时候还有个问题,我们注意到threadRoutine函数是有返回值的,那这个返回值是返回给谁呢?

当然是谁等待它,就返回给谁,一般是返回给主线程,那么主线程如何获取到呢?

自然就是通过pthread_join的第二个参数,retval

retval的类型是void**,而类型之所以是void**,是因为函数threadRoutine的返回值是void*,而void*的地址类型就是void**了

下面改变代码,获取新线程的退出结果:

由于ret中存的就是threadRoutine函数的返回值,我们只需要将ret强转为long long即可得到结果

运行结果为:

可以看到,主线程成功拿到了返回值5

而上面只是最基础的用法,主线程也可以拿到新线程在堆空间开辟的数据,如下:

在threadRoutine函数中,新线程在堆上开辟了一个data数组,主线程等待结束后获取新线程开辟数组的内容

运行结果为:

新线程如果出现异常,主线程也不需要关注,因为如果新线程出现异常退出了,那么主线程同样会退出,所以并不关心这种情况


线程退出

关于线程退出,第一种是直接使用exit

这种方式不推荐使用,因为在线程中使用exit,是直接将整个进程退出了,没有意义

第二种是使用pthread_exit

pthread_exit就只会退出新线程,进程并不会退出,下面演示用法:

我们在threadRoutine函数中,在cout的上面执行pthread_exit,所以就不会执行下面的cout语句

运行结果为:

成功退出新线程,并在主线程中拿到结果

第三种方式是pthread_cancel

pthread_cancel是取消线程

pthread_cancel

同样需要包含头文件pthread.h

函数参数是线程id

下面演示用法:

threadRoutine函数中,新线程死循环

而主线程在main函数中等待4秒,4秒后执行pthread_cancel函数,退出新线程,然后打印线程id,再打印退出结果,最后sleep3秒后主线程也退出

运行结果为:

可以看到打印出来的线程id为上面非常长的一串数字,打印出来的新线程的退出码为-1

通过两个窗口能够更清晰的看出过程,刚开始两个线程都在运行,主线程4秒后执行pthread_cancel取消新线程,这时主线程正在sleep3秒,所以此时只有一个主线程在运行,3秒后主线程也退出,就没有线程正在运行了

我们可以发现,线程被取消,join的时候退出码是-1,也就是宏定义的PTHREAD_CANCELED,即-1


线程id的理解

在上面打印时,发现线程id是非常长的一串数字,其实线程id本质上就是地址,是在共享区的地址,是为了满足线程的私有栈结构的,主线程使用地址空间原始划分的栈结构,创建出来的新线程则在共享区中开辟一段空间,作为线程的私有栈结构,而这段空间的起始地址就是线程id

pthread_self

线程库中还有一个接口,即pthread_self函数,用于每一个线程获得自己的线程id

用法很简单,如下所示:

有了pthread_self函数,就可以和pthread_cancel结合使用,如:pthread_cancel(pthread_self()),可以线程自己取消自己,当然这种用法并不推荐,只是提及一下,还是建议正常使用,即主线程取消新线程,不要自己取消自己

线程之间是共享全局变量的,而如果在全局变量前加__thread(最前面是两个_),就是让每一个线程各自拥有一个全局的变量,这叫做线程的局部存储


线程分离

上面说到主线程可以pthread_join等待新线程结束,但是如果我们并不关心线程的返回值,这时的join是一种负担,这时我们可以告诉系统,当线程退出时,就自动释放线程资源

我们可以给线程设置分离状态,设置完毕后,主线程就不需要再join新线程了,并且新线程退出后会有库自动回收新线程申请的资源,主线程不用关心

pthread_detach

一般都是由新线程自己分离自己,与pthread_self结合使用

需要注意的是,即使线程分离了,分离以后如果线程中出现异常,依旧会影响进程,所有线程都会退出


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVA学习笔记12

1.键盘输入语句 1.1 介绍 ​ *在编程中&#xff0c;需要接收用户输入的数据&#xff0c;就可以使用键盘输入语句来获取。 1.2 步骤 ​ 1.导入该类的所在包&#xff0c;java.util.* ​ 2.创建该类对象&#xff08;声明变量&#xff09; ​ 3.调用里面的功能 import java.…

算法打卡day2|数组篇|Leetcode 977.有序数组的平方、 209.长度最小的子数组、59.螺旋矩阵II

算法题 Leetcode 977.有序数组的平方 题目链接: 977.有序数组的平方 大佬视频讲解&#xff1a;977.有序数组的平方 个人思路 第一时间就只想到暴力解法&#xff0c;双重循环一个循环比较一个循环赋值&#xff1b;但这样可能会超时&#xff0c;所以还能用双指针&#xff0…

halcon中的2D测量-椭圆

一、定义 二维测量指的是测量二维几何图形的参数&#xff0c;例如圆、椭圆、圆弧、矩形的相关参数。这里的参数对圆来说可以是半径&#xff1b;椭圆可以是长半轴、短半轴&#xff1b;矩形则包括宽和高。 二、基本步骤 1.创建测量模型 使用算子 create_metrology_model 2.设…

积分商城管理系统的设计与实现

积分商城管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

设计模式(二)单例模式的七种写法

相关文章设计模式系列 面试的时候&#xff0c;问到许多年轻的Android开发他所会的设计模式是什么&#xff0c;基本上都会提到单例模式&#xff0c;但是对单例模式也是一知半解&#xff0c;在Android开发中我们经常会运用单例模式&#xff0c;所以我们还是要更了解单例模式才对…

【任何电机可使用的七段式s曲线-----包括matlab代码和simulink仿真】

永磁同步电机七段式s曲线 一、前言二、理论分析三、7段式s曲线加减速关系图四、matlab代码代码结果 五、simulink七段式s曲线整体框图simulink结果s-function内嵌代码 六、使用双闭环FOC控制-----simulink仿真示意 一、前言 S形&#xff1a;加加速(T1)→>匀加速(T2)→减加速…

【JVM】StringTable 字符串常量池

参考&#xff1a;javaGuide 字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串&#xff08;String 类&#xff09;专门开辟的一块区域&#xff0c;主要目的是为了避免字符串的重复创建 String的不可变性 1.通过字面量的方式&#xff08;区别于new&#xff09;给一个…

Avalonia学习(二十六)-桌面系统界面Ribbon

这个界面是开源项目中拔下来的&#xff0c;我没有全部改完&#xff0c;只能按照我得界面测试。我还有一个bug没有找到&#xff0c;但是解决了一下。这里没有任何和大家说的&#xff0c;给大家看一下界面效果。 另外地图研究了缩放和显示鼠标位置经纬度

https://htmlunit.sourceforge.io/

https://htmlunit.sourceforge.io/ 爬虫 HtmlUnit – Welcome to HtmlUnit HtmlUnit 3.11.0 API https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit/2.70.0 https://s01.oss.sonatype.org/service/local/repositories/releases/content/org/htmlunit…

Java核心知识点常考面试题(持续更新中)

Java核心知识点常考面试题&#xff08;持续更新中&#xff09; 线程与线程池线程线程池 Java锁机制java线程模型java锁分类轻量级锁重量级锁ReentrantLock 底层原理与源码深度解析ReentrantReadWriteLock 深入理解读写锁CountDownLatchsemaphore 实现公平锁与非公平锁线程死锁与…

Android 9.0 禁用插入耳机时弹出的保护听力对话框

1.前言 在9.0的系统rom定制化开发中,在某些产品中会对耳机音量调节过高限制,在调高到最大音量的70%的时候,会弹出音量过高弹出警告,所以产品 开发的需要要求去掉这个音量弹窗警告功能,接下来就来具体实现这个功能 2.禁用插入耳机时弹出的保护听力对话框的核心类 framework…

第六十八天 APP攻防-XposedFridaHook证书校验反代理代理转发

第68天 APP攻防-Xposed&Frida&Hook&证书校验&反代理&代理转发 知识点&#xff1a; 1、APP防代理绕过-应用&转发 2、APP证书校验类型-单向&双向 3、APP证书校验绕过-Frida&XP框架等 章节点&#xff1a; 1、信息收集-应用&资产提取&权…

蓝桥杯-标题统计

知识点: 关键是考察getline的作用 #include <iostream> using namespace std; int main() { string a; int t0; getline(cin,a);//每次读取一整行并把Enter键生成的换行符抛弃 for(int i0;i<a.length();i){ if(a[i]! )t; } cout<<t; return …

【LTSPICE】宏模型中的语法分析(持续更新)

本篇文章用来总结模型文件、仿真文件中的语法&#xff0c;写给自己看的&#xff0c;格式和内容上比较随意 上图是在安森美官网上下载的一款二极管的spice模型文件。 * 字符串&#xff1a;注释&#xff0c;能看到这篇文章的应该都懂啥叫注释.model&#xff1a;.一个词是命令…

如何修改图片尺寸大小不变形?简单的图片改大小的方法

在平时工作或者学习时&#xff0c;有时候需要将图片的大小进行修改&#xff0c;以便于存储、分享或打印&#xff0c;很多人都习惯性的去下载一些图片处理软件&#xff0c;比较麻烦&#xff0c;这里推荐大家使用图片在线处理工具&#xff0c;打开浏览器直接将图片尺寸修改&#…

【Flink】Flink 中的时间和窗口之窗口(Window)

1. 窗口的概念 Flink是一种流式计算引擎&#xff0c;主要是来处理无界数据流&#xff0c;数据流的数据是一直都有的&#xff0c;等待流结束输入数据获取所有的流数据在做聚合计算是不可能的。为了更方便高效的处理无界流&#xff0c;一种方式就是把无限的流数据切割成有限的数…

【析】装卸一体化车辆路径问题的自适应并行遗传算法

0 引言 国内外有关 &#xff36;&#xff32;&#xff30;&#xff33;&#xff30;&#xff24;的文献较多&#xff0c;求解目标多以最小化车辆行驶距离为主&#xff0c;但现实中可能存在由租赁费用产生的单次派出成本&#xff0c;需要综合考 虑单次派车成本和配送路径成本。…

消息中间件之RocketMQ源码分析(十八)

Broker CommitLog索引机制中的构建过程 1.创建ConsumeQueue和IndexFile。 ConsumeQueue和IndexFile两个索引都是由ReputMessageService类创建的 RequestMessageService类图 ReputMessageService服务启动后的执行过程。 doReput()方法用于创建索引的入口&#xff0c;通常通过…

Redis 管道详解

Redis 管道 关键词&#xff1a;Pipeline Pipeline 简介 Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。通常情况下&#xff0c;一个 Redis 命令的请求、响应遵循以下步骤&#xff1a; 客户端向服务端发送一个查询请求&#xff0c;并监听 Socket 返回&#xff08…

3 easy 26. 删除有序数组中的重复项

双指针&#xff1a; //给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 //一致 。然后返回 nums 中唯一元素的个数。 // // 考虑 nums 的唯…