Linux——线程(2)

在上一篇博客中我介绍了Linux中的线程是什么样的,就如同进程可以通过
fork创建,可以被终止,可以退出一样,线程也可以被我们用户控制,这
篇博客我会介绍线程的控制,并且基于线程的控制所产生的一些问题进行
解决这些问题

线程的控制

1. 线程的创建

我们要学会对线程的控制,首先得创建出来一个线程,所以我们会认识一个接口:
在这里插入图片描述
这就是我们创建线程的一个接口,可以看出它是三号手册中的,并不是系统调用,这一点之后会从多方面来解释它。
它的第一个参数就是线程的id,类型是ptread_t,这是一个输出型参数需要用户自己传入,然后它给你带出线程的id。
第二个参数是有关于线程的属性的参数,我们现在不谈论,传参时传空指针就可以。
第三个参数是一个函数指针,这个函数的参数是void*,返回值也是void*,这个函数也就是线程要执行的函数。
这第四个参数就是第三个参数中函数的参数,它是以这种方式来传的。
下面我们就可以来编写一简单的代码了(其实也是我们上一章博客中所写的):
在这里插入图片描述
在这里插入图片描述
当我们写好代码之后直接使用g++编译的时候会出现下面的情况:
在这里插入图片描述
这其实是因为pthead_create函数,它不是系统调用,它是存在于库中的,但是它也不是C/C++标准库,所以我们在编译的时候要加上这么一个选项:
在这里插入图片描述
而pthread库它有一种叫法,叫做系统原生库,顾名思义就是只要有操作系统,那么就一定会携带这个库。
那么这个库为什么会形成单独的库,而不是和系统代码在一起呢?我们上一章说过Linux中不存在真正意义上的线程,只存在轻量级进程,而对于用户来说,我们刚接触Linux的话哪能知道什么是轻量级进程,我们只知道线程,所以操作系统就必须实现出关于线程的一套接口便于用户使用,这也就会形成了一个pthread库了,不和系统代码在一起是因为Linux中真正意义上根本没有线程这个说法。

a. 多线程的创建

既然能创建一个线程,那我们就能够创建多个线程:
在这里插入图片描述
在这里插入图片描述
在这段代码所演示的结果中我们可以看到几个现象:
task_thread函数被多个执行流同时执行,那就说明这个函数被重入了。
CPU对于线程的调度也是随机的,这一点是理所应当的,因为我们刚开始解除CPU的调度的时候,它的调度就不是完全按顺序来的。
假如现在我们对四号线程进行除0的操作:
在这里插入图片描述
在这里插入图片描述
可以看到一个线程出现异常之后,所有的线程都会被终止,这也印证了我们上一篇博客中所说的。
但是我们发现我们上面的代码有些挫,并且我们要知道线程创建的接口中的那个函数的参数是void*的,这就意味着我们可以传任意类型的参数。那么我们的代码可以是这样的:
在这里插入图片描述

在这里插入图片描述
这里打印出现不规整的现象也是因为CPU调度的时候时间片到了之后切换线程,然后导致输出时,语言缓冲区中的数据出现了错乱。这也说明该函数中含有输出打印函数,这个输出函数也是不可重入的。
我们此时再让这个主线程打印一下线程的id,同时我们再查看一下线程的lwp:
在这里插入图片描述

在这里插入图片描述
我们发现线程的id和lwp竟然不是同一个值,而这其中的原委我会在之后讲述,其实线程id它就是一个地址而已。
在这里我们介绍一个接口,它可以获取调用线程的线程id:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我们可以看到主进程中打印的tid和线程函数中所打印的线程id是相等的,并且我们也可以打印主线程的id。

2. 线程的控制

接下来我们要来认识一些关于线程控制的接口。

a. 线程的终止

我们首先要学会线程的终止
凭借return可以直接终止进程:
在这里插入图片描述

在这里插入图片描述
但是要注意exit函数是用来终止一个进程的,如果把它作用在线程中,它会终止整个线程:
在这里插入图片描述

在这里插入图片描述
除了利用return之外,我们还可以使用pthread库中的函数pthread_exit:

在这里插入图片描述
它的参数跟return 一样能返回函数的返回值:
在这里插入图片描述

在这里插入图片描述

b. 进程的等待

在对进程的认识中进程退出后如果不回收的话,就会进入僵尸状态,而线程也是如此,但是它不是跟进程一样的僵尸状态,而是是类似于僵尸问题。所以就有了线程的回收函数:
在这里插入图片描述
这个函数就能够回收指定线程id的线程,回收成功返回0,回收失败返回一个错误数字:
在这里插入图片描述

在这里插入图片描述
我们在前面只是提及了一下线程执行函数的返回值,但是并没有说怎么获取,我们可以看到,我们是没有办法在主线程中直接得到这个线程函数的返回值的,所以就有了pthread_join的第二个参数。

线程函数的返回值

我们看到线程函数的返回值类型是void* 类型的,而pthread_join的第二个参数是void**的,这其实就是输出型参数,可以回想一下,当我们函数中有输出型参数时,传的其实是这个参数的指针,所以这里才是void*。当使用pthread_join成功回收线程之后,它的第二个参数就会指向线程函数的返回值:
在这里插入图片描述
在这里插入图片描述
就如同我们线程函数可以传任意类型一样,意味着我们的返回值也可以返回任意类型:
在这里插入图片描述
在这里插入图片描述
我们看到确实能够返回任意类型,但是这里的线程id的打印好像有问题,我上面说线程id就是一个地址,这个机器是64位的,所以使用int来存储地址会溢出所以这里我们更改一下:
在这里插入图片描述
在这里插入图片描述
这里可能会有人有问题,假如线程出了异常怎么办?这个等待函数中好像没有waitpid中的status啊,其实无需担心因为线程异常,整个进程都会退出,这就跟这个线程没有关系了,而是进程的问题了。所以编写线程代码时尽量不要让它出现异常。

c. 线程的分离

我们在使用线程的时候有时候并不需要它返回信息,只需要帮我们完成任务之后退出就可以了,所以为了取消pthread_join的消耗,我们可以将这个线程分离:
在这里插入图片描述
这个函数会将指定线程id的线程与主线程分离,然后就不需要关心它的返回和回收它了,这个线程会自动被操作系统回收,且如果主进程是不可以join该线程的。说是分离但其实这些线程还是共享资源,出了异常还是会终止所有进程:
在这里插入图片描述
在这里插入图片描述
当然这个线程分离函数可以让其它线程分离目标线程,也可以自己分离自己。

d. 线程的取消

这是线程终止的第三种方式:
在这里插入图片描述
我们可以来看看一个线程被取消之后,join它会发生什么:
在这里插入图片描述

在这里插入图片描述
我们看到线程函数种返回的是10,但是结果是-1。
这是因为当线程被取消之后,操作系统会将线程函数的返回值设为-1,而这个-1其实是一个宏:
在这里插入图片描述

我们再看线程分离之后能否被取消,以及取消之后能否被join呢?
在这里插入图片描述

在这里插入图片描述
我们能看到线程分离之后仍就能被取消,但是仍然无法join。

较深层次了解pthread库

我们在了解pthread库中的各种接口,但是这些接口都是三号手册中的接口,而它也不存在于C/C++标准库中,我说它其实是原生系统库。
我们这次从用户的视角跳的更高一些,以俯视的视角来认识一下Linux中的线程。我们知道Linux种并没有真正意义上的线程,它是使用进程模拟实现的线程,从而有了轻量级进程,但是对于用户而言,用户在刚使用Linux的时候可能并不认识什么是轻量级进程,他们只认识线程。所以Linux就必须提供关于能够呈现出线程行为的一套体系。但是因为Linux中没有线程,这个就需要在用户层和系统层之间添加一层,使用这一层来让轻量级进程呈现出线程的行为:
在这里插入图片描述
所以,一方面pthread需要向下封装Linux中的轻量级进程,一方面要对用户提供各种线程的接口。
而我们也知道,系统中会存在许多的线程,要完成以上两点,pthread库也必须能够管理这批被模拟成线程的进程,先组织再描述,pthread库中一定会有像struct tcb这样的结构体,这个结构体肯定封装着一个lwp,因为轻量级进程与被模拟出来的线程是一对一的。
而且线程拥有着自己的属性,比如上下文,比如栈空间,这些一定是要被以上的结构体纳入其中的。
在Linux中有这样一个系统调用:
在这里插入图片描述
这个是用来创建轻量级进程的系统调用,它也是创建进程整体的系统调用(fork的底层就是它),具体要创建哪一个这一点可以通过它的第三个参数flag实现。
它的第一个参数就是我们线程控制中创建线程所要执行的函数。第二个函数就是线程所使用的栈空间,可以看到它是一个指针。
那么pthread库又是怎么管理线程的呢?
当我们的程序中使用了pthread库之后,我们的程序在执行之后,pthread库也会被加载到内存中,同时与进程的共享区通过页表建立映射,但是我们说了,系统中会有很多的线程,也会有很多的进程,所以一定会出现多个进程地址空间指向跟pthread库建立映射的,这样pthread库就需要管理系统中的所有的线程:
在这里插入图片描述
而在实际场景中,pthread库是这样组织线程属性的:
在这里插入图片描述
在当线程库与地址空间建立映射之后,线程库中就会创建并初始化出关于线程的各种属性集,然后当再次创建新线程的时候,就会在地址上紧挨着上一个新线程属性集在创建初始化,这样看起来对线程的管理就像对一个数组增删查改一样。
在struct pthread结构体中一定存在这样的字段void* retval来存储线程函数的返回值,从而让pthread_join能够获取返回值。
地址空间中栈只有一个,那么线程的栈空间又是如何分配呢?要知道前面介绍的clone接口中关于栈空间的参数是一个指针,所以线程栈空间可以是在堆上创建,第一个线程使用的就是地址空间的栈区,而新线程的栈空间一般是建立在堆上。
我们看到无论是pthead库的函数接口还是pthread对于线程的组织以及管理的数据,它都是需要在进程的地址空间中的用户区建立的的,所以Linux中的线程也叫用户级线程

理解高级语言中的线程库

我们都知道现在的大部分的编程语言,诸如C++、java、python都支持了对应的线程库,我们现在就来简单的使用一下C++的线程库:
在这里插入图片描述
在这里插入图片描述
当我们编译好它之后再运行它,发现它运行不了,而当我们的编译选项中加上-pthread后,程序能够正常运行:
在这里插入图片描述
由此得知,它也只不过是封装了pthread库从而实现了自己的线程方法而已。

线程局部存储

我们使用一段代码来解释它是什么:
在这里插入图片描述

在这里插入图片描述

这段代码展示了,线程之间是可以共享全局变量的,但是我们在g_flag前加一个这个选项,:
在这里插入图片描述

在这里插入图片描述
这个全局变量好像变成每个线程私有的了,__thread是一个编译选项,它会将修饰的全局变量从所有线程共享的状态,变成每个线程独有的一个变量,而这个变量的存储也从全局数据区到了线程局部存储。
最后我在提供两个知识点,可以自行下去验证,在多线程中,线程是可以创建子进程的,这个子进程仍是该线程所属线程的子进程,线程也是可以使用exec*类的接口进行程序替换的,但是一般不建议,因为一个线程切换了代码块之后,可能会影响其他线程的代码执行。

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

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

相关文章

麒麟信安集控云工作站解决方案,驱动电网奔向数字化转型新未来!

集控站是电网运行信息的集中监控中心,实现对电网设备状态感知、缺陷发现、主动预警、风险管控和应急处置的全流程闭环管控,在保障日常供电方面发挥重要作用。此前集控站主要采用网络KVM矩阵,其数字化转型面临延长距离受限、无法实现跨辖区延伸…

Redis及其常用命令(二)

SortedSet类型 在此类型中,每个元素都有一个分数 key -> string value -> sorted([socre,member],[score,member]...) # 添加元素 zadd key score member # 遍历集合 zrange key start stop [withscores] #升序 zrevrange key start stop [withscores]#降序…

二、vue-cli项目搭建

系列文章: vue实战(商城后台管理系统):http://t.csdnimg.cn/f6Fqa vue.js :http://t.csdnimg.cn/mljxv 目录 系列文章: vue实战(商城后台管理系统):http://t.csdnimg.cn/f6Fqa vue…

O2OA(翱途)流程引擎中如何修改,定制流程的流转记录

使用场景 在特殊使用场景中需要定制流转记录,比如业务上需要修改用户名称,修改时间和意见等. 比如一下场景: 需要将用户"b(company01)"的流转记录修改为"d(company01)". 手工修改 系统默认提供维护工具,在有权限的情况下可以直接进行维护. 通过"左…

AI壁纸号一周增加上千粉丝,轻松变现的成功案例分享

前言 随着AI绘画技术的发展,传统的互联网副业壁纸号在新的技术加持下迎来了第二春。本文将分享一位壁纸号创作者的成功案例,并为大家提供创作门槛和硬件要求等相关信息。 该项目的创作门槛极低,基本上可以由AI完成内容创作。不过&#xff0…

“我快无聊死了”用英语怎么说?柯桥英语口语学习,成人零基础学外语

每日一句 Im bored to death. 我快无聊死了。 单词解析: bored / bɔːd / adj.无聊的,厌倦的 bored to d15857575376eath:指非常无聊或厌烦,达到了极点的程度。 "bored" 和 "boring" 都与无聊相关&#…

Python编程从入门到实践中的一些误区

1.num 使用num时python报错,后来查过后才知道是因为python不支持自增或自减,可以用1。 2.字符串和非字符串连接 要先将非字符串转换为字符串类型之后才能连接 print(2int(‘2’))#4 3.关键字参数必须在未…

基于springboot+vue的电子商务系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 三、库表设计 四、论文 前言 各种购物网站现在已经成了生活中不可缺少的调味品,比如比较全面的淘宝网,还有可以进行交流问答的小红书APP,还有电脑爱好者者们的天堂京东商城等等。拥有一个功能丰富、操作方便的电子商务销售网站,可以汇…

jenkins部署go应用 基于docker-compose

丢弃旧的的构建 github 拉取代码 指定go的编译版本 编写docker-compose 文件 docker-compose.yaml version: "3"services:game-api:image: centos:7working_dir: /appcontainer_name: game-api #自定义command: "./game-api -f conf/config.yaml"por…

针对二进制储存方式深度解析

int main() { int a[4] { 1, 2, 3, 4 }; int* p1 (int*)(&a 1); int* p2 (int*)((int)a 1); printf("%x %x", p1[-1], *p2); return 0; } X86环境下运行 %x打印16进制。 整形指针1跳过四个字节,((int)a 1)强制类型转换为…

【记录 | 基础动态规划】:数字三角形

数字三角形 链接:[USACO1.5] [IOI1994]数字三角形 Number Triangles 题目描述 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 在上面的样例中,从 7 → 3 → 8 → 7 →…

python8综合案例

目标: 1 2 代码 文件的内容读取就完成了 数据的封装 就获得了一个日期的总销售额字典、 pingan 健康

3个小技巧,创建高级简历设计

看厌了简历推荐模板平台千篇一律,您有没有考虑过,自己完成一个独特的简历模板制作?为满足大家量身定做的简历需求,与大家分享一个在即时设计中制作简历模板的三个小技巧。 1、复制/粘贴 进入在线设计新时代过后,许多…

AI情报专刊来啦!《“AI换脸”威胁研究与安全策略》

目录 “AI换脸”常见的诈骗套路 1、伪造账号造谣传谣 2、冒充熟人进行诈骗 3、伪造身份申请银行贷款 4、“网络钓鱼”更加难以识别 5、冒充他人远程面试入职 6、冒名登录盗走银行余额 “AI换脸”的产业链 “AI换脸”使用到的技术 人脸识别和关键点检测 图像/视频合成技术 生成对…

Python 一键批量转化 webp格式图片为jpg

网上爬虫批量下载的图片全部都是webp格式的,需要做格式转换,可以是png或者jpg等等 直接上代码,亲测有效,文件路径自定义即可,后面转化完成后,在文件夹内使用类型排序,然后把webp格式的文件删除…

大数据疑难问题2024

问题一: 集群部署一主一备,初始化操作没有问题,有两个namenode,再次重启显示只有node01有namenode 原因:Journalde服务需要在启动启动hdfs和yarn前再次启动 再次启动步骤: 1.启动3台节点的zookeeper,在3…

多点位移计通气管和灌浆管的布置

在现代工程监测中,多点位移计发挥着不可或缺的角色,尤其在跟踪和记录地下位移动态方面。为了确保多点位移计的精确安装和高效运行,合理设计并实施通气管和灌浆管的布置至关重要。本文将详细探讨多点位移计在正向埋设(向下)和反向埋设(向上)情…

C++特性之一:继承

1. 派生类的成员变量、成员函数、构造、析构 2. 继承的切片 3. 重定义/隐藏 重定义/隐藏:派生类和基类有同名的成员,就叫隐藏。派生类的成员隐藏了基类的成员。 隐藏时可以通过类作用限定符来访问被隐藏的成员。 class Person { public:void Print(){…

ChatGPT提示词Prompts

好、不好的问题 好问题:哪种食物对于狗来说是有毒的?不好的问题:狗喜欢吃什么食物? 好问题:如何学习编程?不好的问题:编程难不难 好问题:如何去除衣服上的污渍?不好的问…

[WUSTCTF2020]朴实无华

查看robots.txt 找到/fAke_flagggg.php 显然这是个假的flag&#xff0c;但是我们在header处发现了fl4g.php 近来发现中文全部变成了乱码 插件转成utf8后正常显示 <?php header(Content-type:text/html;charsetutf-8); error_reporting(0); highlight_file(__file__);//leve…