Linux 进程与线程的区别与联系
一、什么是线程
理解:"进程"有着自己完整的资源(比如代码数据、各个地址的分布...)。线程好比进程内的一个个分支,可以调度'进程'内部的资源,来执行对应的分支任务——类比'厂房'中的'工人'利用进程的内部资源来执行不同的任务。之前学习的进程好比一个执行流,也就是一个线程,全部任务都由这个执行流完成;线程越多,该进程的效率越高,对应越复杂,风险越高!
本质:线程是共享进程资源的实体;进程就是分配系统资源的实体。
概念:线程是进程的执行单元(或执行流),共享进程的资源、独立运行,本质是'轻量化进程'。
二、进程与线程的切换效率
如果要切换一个进程: 需要销毁对应的全部数据,最终也是去除 PCB 结构,然后重新加载新的结构数据,形成新的 PCB。 尤其是'热数据',这是进程间切换导致效率低的一大'痛点':最小都有几千 KB。
'热数据'是需要被高频读取、使用的数据,比如全局变量、函数这些,进程为了避免每次访问都需要去重新加载,就将这些'热数据'放在了 Cache(CPU 高速缓存区)供 CPU 快速找到该类型的数据。
'热数据'对应着'冷数据',即哪些访问频率很低的数据,'热数据'需要跟随进程的访问情况随时替换。
如果要切换一个线程: 线程是和进程共享资源的,线程的退出不需要去换进程 PCB 这些,只有进程的退出才去释放 PCB,所以线程的替换与退出最极端也是加载一些新的代码数据到 CPU 执行,影响很低。
三、虚拟到物理地址的转换
在之前我们知道进程地址空间的虚拟地址转换到物理地址需要借助中间的页表,今天我们再深入!
比如现在有一个 32 位的虚拟地址:X00000001000000010000000100000001 此时虚拟地址会被按照 10、10、12 位被分隔开:0000000100 0000010000 000000000001
前 10 位对应**'页目录索引';中间 10 位对应'页表索引';后 12 位对应'页内偏移'**。
先用前 10 位确定一级页表的位置,一级页表里面存着二级页表的起始位置(2^10=1024)。 中间 10 位再确定二级页表的具体位置,二级页表中存内存中对应页框位置(2^10=1024)。 最后 12 位再确定页框中具体的字节位置((2^12=4096=4KB))。
(后 12 位:操作系统规定'一页内存的大小是 4KB'(4096 字节),而 4096 = 2¹²,所以需要 12 位二进制才能表示'一页内的所有位置'(0 到 4095)。比如上面的后 12 位 000000000001 换算成十进制是 1,意思是'在这一页的第 1 个字节位置'。)
四、线程库的认识
首先需要知道系统是提供了'线程库'的,即线程的系统调用接口,但是后来由于线程库的部分接口很复杂,用户二次封装,出现了用户层面二次封装的'线程库',我们主要学习用户层面的'线程库'。
注意:Linux 中是没有明确的'线程'的概念,只有'轻量化进程'的概念,因此上面的线程内核接口实质是轻量化进程的接口。该线程原库名为**'Pthread 库'**,几乎所有 Linux 平台都已默认携带。
五、线程使用基本常识
- 任何一个线程如果出现错误会导致进程同时关闭。
- 线程是进程的执行分支,如果单个线程出现异常,操作系统也会向进程发送信号,导致该进程和所有线程关闭。
- 既然线程是进程的执行分支,因此进程退了,该进程的所有线程自然也就崩了。
- 不能使用 exit() 终止线程,否则会直接导致整个进程结束,它是用来终止进程的。
六、线程库接口
(1)pthread_t
该接口用于返回系统成功创建线程的 ID,我们需要创建一个变量接收,例如:tid。
pthread_t tid;
(注意:此时只是获取了线程 ID 类型,并没有创建出来。)
此时 tid 就接收了底层系统调用返回的线程 ID。
(2)pthread_create()
原型:
int pthread_create( *thread, *attr, *(*start_routine) ( *), *arg);


