👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
前言
在很多教科书上是这么定义线程的:
- 线程是进程的一个执行分支。
- 线程执行粒度比进程更细。
- 线程就是进程内部的一个执行流。
- …
当初学者看到以上定义,我想他/她的内心是非常崩溃的。那么这篇博客将由浅入深带领大家了解进程的概念等相关知识。
目录
一、 如何理解Linux中的线程
理解线程之前需要先简单回顾一下进程。
程序运行后,该程序的相关代码和数据会被加载到内存中,然后操作系统为其创建对应的数据结构如进程控制块task_struct
,由于进程具有独立性,操作系统还会为进程创建进程地址空间mm_struct
,并通过页表 + MMU
机制建立虚拟地址和物理地址之间的映射关系。
如上,进程要访问代码和数据,必须通过其进程地址空间和页表来完成。换句话说,进程所能看到的资源是通过地址空间来确定的。因此,地址空间可以被视为进程的资源窗口。
由于进程具有独立性,即使是父子进程,操作系统也会为子进程创建独立的进程控制块,并复制父进程的地址空间的映射关系,以实现父子资源共享。需要注意的是,如果子进程尝试修改父进程的共享资源,如代码段,操作系统会启动写时拷贝机制,为修改的值分配新的物理内存空间,并更新页表的映射关系。
总之,当创建进程时,进程控制块、虚拟地址空间和页表映射等是必不可少的组成部分。所以创建一个进程的成本非常高。
为了避免这种繁琐的操作,引入了线程的概念。之前我们一直认为,一个task_struct
对应一个进程地址空间,但其实一个进程地址空间可以对应很多个task_struct
。因此所谓线程其实就是:仅需创建task_struct
,而无需为其单独创建进程地址空间和页表等。相反,该task_struct
可以与已有进程共享其进程地址空间,即线程共享进程的资源窗口,即线程本质在进程的地址空间内运行。
那么现在就可以理解【前言】中线程相关的概念:
-
线程的执行粒度比主进程更细。什么是更细呢?进程是整个程序的一次执行过程,而线程通常只执行进程代码的一部分。所以,线程可以看作是整个进程的执行分支。
-
执行代码(任务)时,我们将其称为执行流。将进程资源合理分配给每个执行流,就形成了线程执行流。并且任何执行流执行都要有资源,即线程要在进程的地址空间内运行。
-
线程的调度成本更低。无论是线程还是进程的上下文切换,都涉及将值加载到
CPU
的寄存器中,以便新的执行体可以继续执行。这一步骤包括将程序计数器PC
等寄存器的值设置为新线程或进程上次中断的位置,以便程序可以继续执行。-
如果切换的是进程而非线程,操作系统可能还需要切换页表,以确保新进程可以访问正确的地址空间,又或者是更新文件描述符表等进程相关的资源状态等。
-
而对于线程来说,因为线程共享同一进程的地址空间和大部分资源(如打开的文件等),因此在切换时不需要切换这些资源,只需要切换线程的私有状态即可。
-
说明:上下文切换是一项开销较大的操作,因为它涉及到保存和恢复大量的状态信息,并且可能涉及到内核态和用户态之间的切换。
【感性理解】
家庭(进程)作为一个整体,有着共同的目标:将家里的日子过好(执行整个程序)。为了实现这个目标,家庭成员(线程)需要各自执行不同的任务。 在家庭中,虽然每个人有自己的任务,但大家都在同一个家庭环境中工作(线程在进程内部运行),家里的房子、车子、家庭预算等是共享的