Linux线程概念篇

Posted Suk-god

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux线程概念篇相关的知识,希望对你有一定的参考价值。

文章目录

1、什么是线程

线程是一个进程内部的控制序列,也被称为执行流,因为在执行用户编写的代码。

1.1线程和进程的关系

形象举例:工厂与流水线的关系
流水线存在的前提是必须有工厂,一个工厂可以拥有多条流水线~

将这样的关系映射到进程与线程中,可以得到如下结论:
结论1:线程是依附于进程才能存在的,如果没有进程,线程无法单独存在
结论2:多线程的存在是为了提升整个程序的运行效率

1.2分析曾经写的代码

1.2.1 之前我们对于进程的理解如下:

进程在内核当中是一个task_struct结构体,在该结构体中有一个成员变量 pid_t pid;被我们称之为进程号

1.2.2上面的理解稍有偏差,我们结合线程再次理解task_struct结构体

(1)Linux操作系统当中没有线程的概念,我们所说的创建线程,本质上是在Linux操作系统中创建轻量级进程(lwp),因此,在Linux操作系统中,轻量级进程等价于线程

那么,我们之前写的代码,有没有线程呢?
答案是 :!曾经写的代码当中存在线程,就是执行main函数的执行流,我们称之为主线程程序员创建的线程被称之为 工作线程

(2)重新理解pid
pid本质上是轻量级进程即线程的id
解释:
在task_struct结构体当中,有两个成员变量,分别是pid和tgid,他们两的具体含义见下图:

疑问:为什么之前要说pid就是进程id呢?
本质上来讲,pid是轻量级进程即线程id,但是在之前的代码当中,由于只有一个主线程的执行流,因此主线程的线程id(pid)和进程的进程id(tgid)是一样的。

2、内核角度理解线程

对于上面讲述的内容,我们通过画图的方式进一步理解
1、每一个线程,在Linux中都会使用一个task_struct结构体对其进行描述
2、同一个进程里面的所有线程共用同一块进程虚拟地址空间
3、每一个线程都有自己的线程id(pid),它们互不相同
4、同一个进程中的每一个线程的tgid都是相同的,因为他们属于同一个进程。并且该进程的tgid与主线程的pid是相等的
🆗,上图展示:

主线程和工作线程的pid互不相同,他们的tgid是相等的,并且和主线程的pid一样

3、线程的共享与独有

3.1共享的内容:


1、文件描述符表
对于统一进程中的任意一个线程,只要该线程打开了某一个文件,该文件的文件描述符信息就会被添加至fd_array[]的数组当中。对于每一个线程来说,该信息是透明的,也就是说每一个线程都可以访问并获取该信息。

2、用户id 与 用户组id
谁创建的进程,该进程拿到的就是谁的用户(组)id
3、信号处理方式
信号处理方式本质上是操作系统内核定义的
因此,每个信号的含义和处理方式是固定的,并不会说不同线程的同一信号处理方式不一样。
4、当前进程的工作目录
同一进程的所有线程都可以知道当前进程是在系统中的哪个路径下工作的,他们拿到的目录是一样的。

3.2 独有的内容


1、调用栈

问题:为什么调用栈是独有的呢?不同的线程共用同一个栈会怎样呢?

要解决这个问题,首先我们需要了解一些铺垫知识:
函数调用是有成本的,需要在栈上开辟空间,该块空间被称为该函数调用时的栈帧
②函数调用完毕后,栈帧被释放。释放栈帧的顺序满足栈的特性–后进先出;也就是说,后被调用的函数先退出。换句话说,先被调用的函数要想退出,必须等后调用的函数退出后它才可以退出

了解到这里,接下来我们以画图的方式阐述 线程独有调用栈的必要性

上图阐述了共用同一个栈会带来的问题----调用栈混乱
下面,我们通过一个接口再次理解调用栈混乱:

pid_t vfork(void);

该函数与fork的功能一样,就是创建一个子进程
唯一不同的是:
fork创建出来的子进程拥有自己的进程虚拟地址空间,
而vfork创建出来的子进程和父进程共用同一块虚拟地址空间。

因此vfork按道理说也会存在上述的调用栈混乱问题。vfork为了解决这个问题,规定,父进程在使用vfork创建子进程都就被阻塞。只有等到子进程退出后,父进程才能正常调用函数


鉴于上面的调用栈混乱问题,我们就需要为每个线程分配独有的栈空间。这样就能避免上述情况的发生。
注意:
主线程使用的是进程虚拟地址空间的那块栈空间
其他的工作线程则是在进程虚拟地址空间的共享区中为其分配相应的栈空间

2、寄存器
每一个线程都是独立被调度的,一个线程在拿到CPU资源后执行它的代码,但是总会有被调度器切换下来的时候,此时该线程就需要将它的执行相关信息通过寄存器保存下来。
3、线程ID
每个线程都拥有各自的pid
4、errno
每个线程是独立地执行它的代码的,如果说某一个线程执行过程中出现错误,操作系统应该将错误码信息存放在该线程的errno当中。
5、信号屏蔽字
就是进程信号处的阻塞位图
①每一个线程都是一个task_struct结构体,而阻塞位图就存在于该结构体中
②线程单独被调度,此时收到的信号并不是针对整个进程,而是针对某一个线程的。因此,每个线程都要有自己的信号屏蔽字
6、调度优先级
既然每个线程是相互独立的,那么它的调度优先级也是独立的。

4、线程的优缺点

4.1 优点


1、多线程的程序,拥有多个执行流,合理使用,可以提高程序的运行效率
e.g:注意要合理使用,确保正确的前提下使用多线程才会提高程序的运行效率,否则等于是反向提高。

2、多线程程序的线程切换比多进程程序快
由于线程是共用同一块虚拟地址空间,因此在线程切换的时候,有一些数据就不需要再被切换出去了,因此也就节省了切换所耗费的时间。

3、可以充分发挥多核CPU并行的优势,体现在两个方面:
(1)计算密集型的程序,可以进行拆分,让不同的线程计算不一样的事情
比如说我们现在需要求1000!,我们可以给出合适的线程数量去分别计算整个过程的一部分,然后再将最后计算的结果组合起来得到最终的结果

(2)I/O密集型的程序,可以进行拆分,让不同的线程执行不同的I/O操作,可以不用串行运行,提高程序运行效率
比如现在需要去读取n个文件的内容,如果只有一个线程的话,只能按照顺序一个文件一个文件的读取。而多线程则可以让多个线程去分别读取指定的文件。这样也就提高了程序的运行效率。

4.2 缺点

1、编写代码的难度更高
线程是通过操作系统进行调度的,而每一个线程都是抢占式执行的。因此我们并不清楚线程执行的先后顺序。
假设现在有两个线程,线程A 和线程B。有一个业务场景是必须让A先执行B后执行,否则结果就会出错。此时我们就需要对线程执行的代码进行限制,确保A能够先被执行。因此编写代码的难度增大。

2、代码的(稳定性,健壮性)鲁棒性要求更加高
需要合理分配资源,否则可能会由于某一个线程的崩溃而导致整个进程随之崩溃,无法运行。
3、线程数量并不是越多越好
因为对于一台特定的机器,它的CPU的核数数固定的。如果线程过于多,反倒有可能导致程序运行的效率低下。因为线程过多,线程切换耗费了大量的时间。
面试题:现在有一个程序xxx,你认为启动多少个线程合适?
解答:程序的运行效率要基于某一机器的具体配置,经过测试后才能够得出结论.
4、缺乏访问控制,可能会导致程序产生二义性结果
由于时序问题,我们无法预估线程执行的先后顺序,因此可能会导致程序结果的二义性。我们可以通过互斥等技术控制线程的访问时序,进而解决程序结果的二义性。
5、一个线程崩溃,会导致整个进程退出

🆗,概念相关的总结到这里就结束了,下篇我们谈谈线程控制相关的内容~
我们下篇见!!!

以上是关于Linux线程概念篇的主要内容,如果未能解决你的问题,请参考以下文章

Android集成腾讯云IM(下篇)

Java内存与垃圾回收篇(对象内存与垃圾回收机制)下篇

Linux调度系统全景指南(下篇)

CPU篇---理清“核“概念-01

并发编程基础篇——第一章(并发相关基础概念理解)

Linux篇第十三篇——多线程(线程概念+线程控制)