Linux:线程控制

一、线程库

在Linux中,内核中并没有很明确的线程概念,而是只有轻量级进程的概念!!因此OS并没有给我们提供线程的系统调用,只会给我们提供轻量级进程的系统调用

------>可是我们的用户只认识线程而不认识什么轻量级进程啊!!而且使用起来的学习成本也很高啊! 因此就有大佬在应用层为轻量级进程接口进行封装,为用户提供直接的线程接口(pthread线程库)

pthread线程库又叫原生线程库,几乎所有的Linux平台都是默认自带这个库的,但是他对于g++来说属于第三方库,链接这些线程函数库时要使用编译器命令的"-lpthread"选项**!**

二、线程创建pthread_create

功能:创建一个新的线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数

thread:返回线程ID(输出型参数)

attr:设置线程的属性,attr为NULL表示使用默认属性(一般设为NULL

start_routine:是个函数地址,线程启动后要执行的函数(其实就是通过要执行的函数来给线程划分地址空间)

arg:传给线程启动函数的参数(可以通过类传多个)

返回值:成功返回0;失败返回错误码(pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回)

pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码(局部存储)。对于pthreads函数的错误, 建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小

2.1 简单看看多线程

为什么-l就可以了呢??------>因为这个库已经默认安装在系统路径下了,编译器知道他在哪,只是不知道要链接哪个库而已!

如果我们想查看所有的轻量级进程的话 可以用 ps -aL(a是all的意思,L是轻的意思)

我们会发现主线程的PID和LWP(cpu调度的基本单位)是一样的,这应该也是用来让CPU区分切换的是主线程还是次线程的一个标识!

监视线程的方法:

2.2 全局变量

所有的线程都可以看到全局变量

我们会发现以前的进程间通信,无论是管道、共享内存、消息队列......他们让两个进程看到同一份代码和资源的方法都比较麻烦,可以线程天生就具有看到同一份资源的能力,所以也给我们的通信提供了很好的应用场景和技术准备!

问题:为什么我们不研究多进程并发,而是研究多线程并发呢??

------>因为多进程的写时拷贝、通信......都很麻烦,而线程的共享性更容易实现,他是先进性的表现,但是方便的同时也伴随着线程之间的相互影响、健壮性差等问题,因此这些需要我们程序员在代码上去解决这类问题!

2.3 tid vs LWP

我们会发现tid和LWP差距非常大,因为lwp是操作系统的轻量级进程的概念,只需要OS知道就行,而tid是给用户使用的!(本质是一个地址)

2.4 线程函数参数返回值为啥都是void*

以往进程返回是通过返回错误码来告知我们错误信息,可以线程中的函数为什么会是void*呢??

因为不止可以传整形、字符串......还可以传类对象!! (类里面可以放很多内置类型,其实就相当于可以传很多参数,以及返回很多返回值)

即使你只想传一个整形或者字符串,你也可以封装在类里面传,能传类的话尽量传类,因为他具有可扩展性!未来想增加别的类型就很方便

比方说我们要计算1-100相加,我们可以写个request的类传递给他1-100的区间,然后再写个Respond的类帮助我们把运行结果返回回来!!

要注意一定不要在主线程里面创建局部变量传递给次线程!

如果我们主线程要传类对象给次线程,就必须在堆区开辟空间,这样虽然td指针被释放了,但是我们可以通过args把这个指针传递给线程,这样每个线程就可以去访问自己在堆中的对象了!

其实堆区的资源大家都看得到,比如我2号线程也可以去看1号线程堆区的数据,但是这样没有意义!!所以线程可以看到全部的堆空间,但是每个线程访问的是堆的不同位置!

问题:可是我们为什么不直接在线程里去写这个参数,而是要让主线程通过类传递过去呢??

------> 因为主线程可能需要给不止一个次线程分配任务,比如说我想让1线程算1-100,让2线程算101-200...... 也就是可以让每个线程并行地去共同完成同一个任务,而我只需要讲需要处理的数据通过类告诉他们就行,最后我再对结果进行汇总(主线程重分配和管理,次线程重实践)

------>甚至你还可以把方法都写进类里面!!这样你的线程就更简洁了!!

**------>**你次线程需要什么类,需要什么方法,我可以通过类来告诉你!!你只管调用就行!

三、线程等待pthread_ join

你主线程把我新线程创建出来了,你不得管我吗??万一我还没退你先退了怎么办??

------>所以我们要尽量保证主线程最后退!

怎么让主线程最后退呢??你可能会想到写个死循环,然后把工作都交给次线程去干,这是这样真的好吗??我只是想让你管理我,不是想让你当甩手掌柜然后自己啥代码也不执行,而且我要是自己退了,你就搁那傻傻循环啥也不管吗??你难道不关心我的运行结果吗??你难道不需要释放我的空间吗??

------>所以你主线程必须要等待子线程!!(1、将已经退出的线程的空间释放掉 2、创建新的线程时不会复用刚在退出线程的地址空间)

功能:等待线程结束

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_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED
  1. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。

  2. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数

问题:为什么是void**呢??

------> 因为OS作为管理者也需要知道执行结果,这个执行结果会先被携带结构体里,然后我们可以通过二级指针将我们自己的void*变量地址传递给他,然后把他拷贝过来!

四、线程分离pthread_detach和pthread_self

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join(需要由主线程回收)操作,否则无法释放 资源,从而造成系统泄漏。

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

讲个小故事理解分离:

比如说五六十年代那个时候,很多家庭里面会一次性有很多小孩由父母管理,然后这些小孩长大以后,有其中一个小孩跟父亲特别不对付(一种情况是小孩自己提出分家------子线程自己想分离 还有一种情况是父亲嫌弃你让你离开------父线程要求你分离),而这个小孩虽然可以共享家里的一部分资源,但是其实已经不是一家人了!!所以不管你以后怎么样了,父亲都不会管你了(就相当于线程分离之后,虽然他还可以用到进程的公共资源,并且他也有自己idea资源,但是父线程已经不关心他了 此时以后不管怎么都没人管他 只能自生自灭由OS去回收他)

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

pthread_detach:

int pthread_detach(pthread_t thread);

pthread_self

pthread_t pthread_self(void); 可以获得线程自身的ID

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。------本质上就是将我们线程库中我们认为的tcp结构体里的一个关于线程是否分离的标记位给改了!!

五、线程终止pthread_exit和pthread_cancel

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

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

  2. 线程可以调用pthread_ exit终止自己。

  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

pthread_exit函数

功能:线程终止

void pthread_exit(void *value_ptr);

参数

value_ptr:value_ptr不要指向一个局部变量(独立栈空间会被释放)。

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(线程都终止了返回没有意义)

pthread_cancel函数

功能:取消一个执行中的线程

int pthread_cancel(pthread_t thread);

参数

thread:线程ID

返回值:成功返回0;失败返回错误码

六、c++的线程库

C++其实也有自己的线程库thread ! 可实际上他的底层也是封装的pthread的原生线程库!也需要指定链接

而以往我们在windows系统下在vs中使用线程库,我们其实并不需要这样,这是因为windows下他有自己专门的线程库,因为windows实现的时候就是有专门的tcb结构体,所以我们包cpp头文件的时候,他的底层其实是windows类型的系统调用!

------>cpp具有跨平台性,根据不同的平台(Linux和windows),他用的是条件编译,外面虽然呈现出来的头文件和接口是一样的,但是不同的平台内部封装所使用的系统调用是不一样的!!

------>所以在Linux下的cpp底层封装的是Linux的原生线程库(由于是用的进程模拟线程,所以并没有专门的tcb结构体,他的系统调用接口只有轻量级进程的概念,所以又封装了一个原生线程库给我们,而使用第三方库都需要链接,cpp底层也是这个原生线程库,所以也要链接) 而windows下的线程库就是原生windows下的系统调用,所以他并没有第三方库的概念!

------>所以你平时写代码在不同的环境下没有感觉,是写库文件的设计者帮助你把这种差异给屏蔽掉了!! 所以我们平时刚推荐使用语言里的库方法而非系统调用接口,因为这样代码就不具备可移植性和跨平台性了!

七、用户级线程vs内核级LWP

用户级线程和内核级LWP是1:1的关系

线程共享进程数据,但也拥有自己的一部分数据:

线程ID

一组寄存器(保存上下文)

独立栈 (完成调用链)

errno (局部存储)

信号屏蔽字

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

文件描述符表

每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)

当前工作目录

用户id和组id

1、 线程的概念是库给我们维护的!!所以线程库注定要维护多个线程属性集合!------>先描述再组织

2、不用维护线程的执行流,这是由OS的轻量级进程完成的(已经帮我们封装了)

3、原生线程库必然要被加载到内存中,因此我们的线程属性集合也应该在线程库中维护

4、线程控制块就是库帮我们维护的一个用户级线程结构体tcb,而pthread_t类型的线程ID,本质 就是一个进程地址空间上的一个地址,就是指向他的。

7.1 独立线程栈

执行流的本质就是独立的调用链!! 所以每个线程都需要建立自己独立的调用链,所以就必须得有一个独立的栈结构

------>支持我们在应用层来完成我们整个调用链对应的临时空间的开辟和释放

------>这样对于局部变量来说,就可以保持线程的独立性

虽然是独立栈,但其实其他线程想要访问在技术角度也是可以做到的(定义一个全局的指针,然后在某一个线程中让他保存其中的一个局部变量的地址,然后主线程再当全部线程创建完成之后,再去查看这个全局的指针变量),因为线程与线程之间几乎没有秘密!!

7.2 局部存储

如果我们想要一个只属于线程的全局变量呢??------>通过局部存储(他会被存储在一个区域中 )

问题:可是这看上去很鸡肋啊!!干嘛要定义这种私有的全局变量啊,我直接在自己的独立栈定义局部变量不就行了??

------>可是如果你的线程内部将来也调用函数了呢??比如说你想让别的函数也能够知道你线程的id或者是其他属性,那你还得把这个局部变量通过参数传递给他!! 所以局部存储私有的全局变量最核心的意义就是可以让该线程独立栈内部调用链上所有的函数都可以看得到这些信息,而不需要传参或者是频繁地调用系统调用!

相关推荐
猫咪-952710 分钟前
mv指令详解
linux·指令
叶 落18 分钟前
Ubuntu 下载安装 kibana8.7.1
服务器·ubuntu·kibana·es
大霞上仙18 分钟前
jenkins入门7 --发送邮件1
运维·jenkins
鲁子狄23 分钟前
[笔记] Jenkins 安装与配置全攻略:Ubuntu 从零开始搭建持续集成环境
java·linux·运维·笔记·ubuntu·ci/cd·jenkins
endswel23 分钟前
Jenkins pipeline 发送邮件及包含附件
运维·jenkins·邮件
坐忘3GQ23 分钟前
119.使用AI Agent解决问题:Jenkins build Pipeline时,提示npm ERR! errno FETCH_ERROR
运维·npm·jenkins·nodejs·jenkinsfile·文心快码·fetch_error
蚊子爱喝水1 小时前
Centos7使用yum工具出现 Could not resolve host: mirrorlist.centos.org
linux·运维·centos
☆凡尘清心☆1 小时前
CentOS Stream 9上安装配置NFS
linux·运维·centos
JaneZJW1 小时前
嵌入式岗位面试八股文(篇三 操作系统(下))
linux·stm32·面试·嵌入式·c
网络安全-杰克1 小时前
网络安全概论——入侵检测系统IDS
服务器·安全·web安全