做买东西的网站要多少钱,百度网盘做自已网站,这么做输入文字的网站,做鞋子出口需要作网站吗欢迎来到博主专栏#xff1a;从0开始linux 博主ID#xff1a;代码小豪 文章目录 进程与线程线程概念线程的优点线程的独立数据 进程与线程
如果要理解线程#xff0c;那么进程将会时绕不开的点。首先我们回顾一下我们之前在进程章节当中是如何描述进程的#xff1f; 进程从0开始linux 博主ID代码小豪 文章目录 进程与线程线程概念线程的优点线程的独立数据 进程与线程
如果要理解线程那么进程将会时绕不开的点。首先我们回顾一下我们之前在进程章节当中是如何描述进程的 进程内核数据结构代码和数据 代码和数据很好理解我们在c/c文件当中写的代码和数据经过编译后形成的二进制可执行程序中数据和指令都已二进制的形式保存在文件中。而内核数据结构指的是PCB(task_struct)mm_structvm_area_struct进程地址空间还有页表等等。这些存在于内核当中描述与管理进程的相关数据结构。
那么线程是什么呢
这里博主先从其中一个概念开始展开线程是进程中的一个执行流是进程的执行分支。
那么问题来了什么是执行流呢
我们可以将流看做是进程中连续的cpu指令cpu在执行这些指令流就叫做执行流。而根据进程地址空间我们的cpu执行的指令、数据的地址都是保存在进程地址空间当中的而线程相当于是将这个进程地址空间的一部分切割开来作为自己的地址空间让cpu从中处理。那么此时进程就分为了两个执行流继续执行主程序的叫做主线程而执行分支程序的就是分支线程。 多说无益我们尝试创建一个线程创建线程用到的是C语言posix库中的函数pthread_create,关于这posix库是什么我们在下一篇章节再说pthread_create的函数原型如下 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
这些参数博主先不多介绍首先start_routine是一个函数指针即分支线程开始的函数地址我们可以看成是线程的main函数。pthread_t是线程号线程号我们后面再说。我们只需要传入一个pthread_t的参数的地址即可。
那么程序如下
void* Thread1Run(void* addr)//分支线程的入口函数
{while(true){std::couthello 我是线程1std::endl;sleep(1);}
}int main()
{pthread_t tid;//创建分支线程pthread_create(tid,nullptr,Thread1Run,(void*)thread-1);while(true){std::couthello 我是主线程std::endl;sleep(1);}return 0;
}这段程序的逻辑如下当进程运行时此时内部没有分支线程当执行pthread_create后创建出一个分支线程。该线程执行Thread1Run函数的逻辑。此时进程1内部就出现了两个执行流主执行流继续执行主函数的代码而分支线程执行Thread1Run的代码。
运行结果如下 从这里可以看出主线程和分支线程是并行的如果不是并行的那么主线程压根不会执行pthread_create中的代码。
线程概念
如果线程可以并行那么一定是需要linux系统对线程进行调度的就像进程调度那样。那么既然linux要对线程管理起来linux是如何管理线程的呢老生常谈linux管理进程是通过创建内核数据结构描述进程接着通过内核数据结构来进行管理我们将这个操作称为先描述再组织。
那么线程又要如何描述呢我们先来思考这么一个问题我们需要线程有什么首先线程要有描述符这样操作系统才能通过描述符找到要操作的线程接着线程要有执行的代码和数据因此要有自己的地址空间线程也可以通信、因此还要有文件描述符表fd_array。线程还要能接收到信号因此要有pending表、block表、handler表。咦这么看下来线程的内核数据结构好像和进程的内核数据结构一模一样啊
没错线程和进程用的内核数据都是一样的即task_struct
这么一搞、我们就要对一个问题进行思考了。线程是什么进程又是什么
首先博主为进程和线程先下一个定义线程是操作系统调度的最小单位、进程是承载分配资源的基本实体。
在我们上一个例子中已经看到cpu会在线程之间进行切换调度其原理和进程之间的切换调度类似。那么如何理解进程时承载分配资源的基本实体呢首先我们来思考一个进程需要什么资源
首先进程要有地址空间吧代码、数据、堆区、栈区这些都是实打实存在于内存当中的第二个进程会打开文件吧这些文件都也是要加载到内存当中的。这些就叫做资源。
那么线程有没有这些呢答案是有但是为什么说进程承载资源而不是线程承载资源呢?这是因为由线程申请的资源都是放到进程名下的如何理解这句话呢我们在前面不是说了吗将进程空间的一部分代码、数据分割给分支线程线程就创建出来了那么我们逆推一下进程主线程分支线程
我们之前所见过的进程是不是没有线程呢非也而是这些进程当中只有一个主线程因此我们在前面所说的进程调度本质上也是线程调度只是这个线程只存在一个而已。那么现在我们知道了线程申请的一切资源都是放在进程名下的这也意味着线程之间使用的资源都是共享的即同一进程下的线程使用的地址空间是相同的使用的文件描述表也是相同的那么理解了这个对于线程和进程的关系就很明了了。
我们可以以现实举个例子比如我们从寝室走到教室那么我们这个人就是一个进程执行的任务就是到达教室而我们的左脚、右脚、左腿、右腿则是进程下的线程各自执行自己的程序但是目的都是为了完成一个进程的任务。这些线程所使用的资源是共享的即人体身上的能量你总不能说你的左腿运动和右腿运动消耗的能量不属于同一个人吧
在linux中线程和进程其实并没有太大的区别因此在linux当中线程被称为轻量级进程又称LWPlight weight process。所谓的cpu对进程的调度无论是进程还是轻量级进程它们的权重没有任何区别。而且我们还能查到轻量级进程的属性通过指令ps -aL可以查看但是考虑到让轻量级进程与进程之间有所区别因此后面还是叫线程和进程。
线程的优点
创建⼀个新线程的代价要比创建⼀个新进程小得多
我们fork出一个新进程由于进程之前具有独立性因此新进程要有自己的地址空间要有自己的内核数据结构。而线程之间都是共享进程的资源的因此创建一个新线程只需要创建一个新的内核数据结构来描述轻量级进程就行。
线程之间切换比进程之间切换的速度快
为什么呢因为进程之间具有独立性不同的进程其代码数据不同使用的进程地址空间也不同页表不同打开的文件也不同这些东西都是要实打实加载到内存的。要加载就是要让磁盘与内存之间做慢速I/O因此速度变慢。
而且在CPU当中也有体现不同的进程其上下文也是不一样的并不是说线程的上下文就相同而是进程的上下文更复杂。因为在CPU当中存在两个东西一个叫做TLB一个叫做cache。这两个硬件都是用来做缓存的。TLB负责的是页表缓存不同的进程拥有不同的页表因此TLB肯定不能复用因此切换进程的时候TLB要重新作缓存而线程之间由于使用的是相同的进程空间因此在切换线程的时候TLB就不用更新。
而cache我们还没认识过现在我们就来讲讲。当一个进程运行时我们要将进程中的数据和代码加载到内存当中因为cpu与内存之间做交互的速度远快于cpu与磁盘做交互但是cpu和内存之间的交互是最快的吗当然不是而是cpu与cache之间的交互才是最快的因此当cpu执行一个代码时会将代码附近的所有数据都加载到cache当中我们可以输入指令cat /proc/cpuinfo可以查看到cpu信息其中就包括cache的大小
我们可以看到一个cached可以缓存40M左右的数据因此当cpu执行一个指令时首先会查该指令的地址是否在cache当中如果没有再去内存当中找顺便在将cache当中缓存的数据更新成新执行的指令附近40M的数据。
那么为什么说cache能提高效率呢这其实是一个概率性的问题因为当计算机执行完一个代码后其第二条代码大概率在该代码的下一条。因此将代码附近的数据加载到cache中可以大大减少cpu访问内存的次数。但是如果你能写出让cpu访问的代码不停跳转的程序那么这个cache就相当于是负优化了因为cache不仅没有让你减少cpu访问内存的频率还要不停地加载内存中的数据到cache当中这个时间只会更长。因此这个基于cache缓存以提高cpu效率的方法我们称为局部性原理。因此我们所写的代码和数据要尽可能的减少不必要的跳转以提升程序的运行速度。
那么我们回到进程由于进程使用的内存空间是不同的因此cache缓存的数据是基本不可能让两个进程一起用的。因此切换进程的时候cache当中的数据也要更新一下这不也导致了cpu效率变慢了吗而线程之间由于使用的资源都是在一个进程当中的因此cache缓存大概率不用更新因此切换线程所需要的时间开销也会变小。
但是线程并非完全没有缺点。 • 性能损失 ◦ ⼀个很少被外部事件阻塞的计算密集型线程往往⽆法与其它线程共享同⼀个处理器。如果计 算密集型线程的数量⽐可⽤的处理器多那么可能会有较⼤的性能损失这⾥的性能损失指 的是增加了额外的同步和调度开销⽽可⽤的资源不变。 • 健壮性降低 ◦ 编写多线程需要更全⾯更深⼊的考虑在⼀个多线程程序⾥因时间分配上的细微偏差或者 因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的换句话说线程之间是缺乏保护 的。 • 缺乏访问控制 ◦ 进程是访问控制的基本粒度在⼀个线程中调⽤某些OS函数会对整个进程造成影响。 这里我们做简单的了解博主在后面的章节当中还会继续说明
线程的独立数据
进程当中有哪些资源是线程之间共享的而线程之间又有什么资源是不互通的
共享的资源有
进程地址空间信号所有线程对于信号的处理方式都是一致的异常如果一个线程出现了异常那么整个进程都会崩溃 其实这也很好理解比如广西周某偷了电瓶总不能说是我的手偷的而不是总体偷的进而免除惩罚吧由于线程作为进程的一部分所有的线程的目的都是为了让进程完成某个任务因此如果一个线程出现了异常那么进程的任务还能完成吗因此将进程中所有线程一起处理才是正确的处理方法。文件描述符
不共享的资源有
LWP:线程ID信号屏蔽字即block表虽然线程之间对待信号的处理方法一致但是线程可以选择屏蔽掉啊线程的上下文数据虽然使用的资源是相同的但是它们具体执行的指令可不同因此cpu在切换线程时它们的上下文数据也要进行切换只是切换线程的复杂度远低于切换进程。栈errno调度优先级