Linux : 多线程【线程概念】
Linux : 多线程【线程概念】
- (一)线程概念
- 线程是什么
- 用户层的线程
- linux中PID与LWP的关系
- (二) 进程地址空间页表
- (三) 线程总结
- 线程的优点
- 线程的缺点
- 线程异常
- 线程用途
(一)线程概念
线程是什么
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
- 一切进程至少都有一个执行线程。
- 线程在进程内部运行,本质是在进程地址空间内运行。
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更轻量化。
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
进程是资源分配的基本单位,线程是cpu执行调度的基本单位。一个进程创造不仅只有task_struck(PCB)还有页表、进程地址空间等等。而一个线程的创建只有task_struct并且沿用进程的地址空间和页表等,所以线程的创建十分轻量。
- 一个线程都是当前进程里面的一个执行流
- 线程在进程内部运行,本质就是线程在进程地址空间内运行,也就是说曾经这个进程申请的所有资源,几乎都是被所有线程共享的。
cpu是通过task_struct来执行调度,而一个进程中是可以有多个线程即执行流的,所以线程的执行粒度是比进程细(即线程是执行进程的一部分代码)
用户层的线程
进程和线程是如此的相似,那么究竟是如何创建线程的呢??
实际上在Linux中没有真正意义的线程,那么也就绝对没有真正意义上的线程相关的系统调用
想要用好线程就必须要对线程做管理,进程已经有一套进程的管理模式了,而描述线程的控制块和描述进程的控制块是类似的,那么再对线程做一套管理算法等等就十分繁琐,因此Linux并没有重新为线程设计数据结构,而是直接复用了进程控制块,所以我们说Linux中的所有执行流都叫做轻量级进程。
(在windows系统中真正实现了线程,因此Windows操作系统系统的实现逻辑一定比Linux操作系统的实现逻辑要复杂得多。)
在linux操作系统没有线程的概念,但是我们用户却需要一个真正的线程的概念,所以原生线程库pthread由此诞生。原生线程库实际就是对轻量级进程的系统调用进行了封装,在用户层模拟实现了一套线程相关的接口。因此对于我们来讲,在Linux下学习线程实际上就是学习在用户层模拟实现的这一套接口,而并非操作系统的接口。
linux中PID与LWP的关系
进程是由操作系统将程序运行所需地址空间、映射关系、代码和数据打包后的资源包,而 线程/轻量级线程/执行流 则是利用资源完成任务的基本单位
- 进程是承担系统资源分配的实体
- 线程是 CPU 运行的基本单位
实际上 进程 = 线程 + 虚拟地址空间 + 映射关系 + 代码和数据,这才是一个完整的概念
使用 ps -aL
指令查看所有的线程
LWP就是我们的轻量级进程也就是线程,每一个线程都有自己的LWP号,而线程组成线程组,我们用pid来描述这个线程组,也就是我们常说的一个进程的pid。
(二) 进程地址空间页表
在 32 位系统中,存在 2^32 个地址(一个内存单元大小是 1byte),意味着虚拟地址空间 的大小为 4GB。
通常将这32位分为 10、10、12
- 第一个10: 虚拟地址中的前 10 个比特位,用于寻址 二级页表
- 第二个10:虚拟地址中间的 10 个比特位,用于寻找 页框起始地址
- 第三个12:虚拟地址中的后 12 个比特位,用于定位 具体地址(偏移量)
- 选择虚拟地址的前10个比特位在页目录当中进行查找,找到对应的二级页表。
- 再选择虚拟地址的10个比特位在对应的页表当中进行查找,找到物理内存中对应页框的起始地址(一个页框4KB)。
- 最后将虚拟地址中剩下的12个比特位作为偏移量从对应页框的起始地址处向后进行偏移,找到物理内存中某一个对应的字节数据。
说明:
- 物理内存实际是被划分成一个个4KB大小的页框的,而磁盘上的程序也是被划分成一个个4KB大小的页帧的,当内存和磁盘进行数据交换时也就是以4KB大小为单位进行加载和保存的。
- 4KB实际上就是2的12次方个字节,也就是说一个页框中有2的12次方个字节,而访问内存的基本大小是1字节,因此一个页框中就有2的12次方个地址,于是我们就可以将剩下的12个比特位作为偏移量,从页框的起始地址处开始向后进行偏移,从而找到物理内存中某一个对应字节数据。
上面所说的所有映射过程,都是由**MMU(MemoryManagementUnit)**这个硬件完成的,该硬件是集成在CPU内的。页表是一种软件映射,MMU是一种硬件映射,所以计算机进行虚拟地址到物理地址的转化采用的是软硬件结合的方式。
注意:32位计算机下是二级页表,64位平台下用的是多级页表。
当引发异常操作时,操作系统能在 查页表 阶段就进行拦截,而不是等到真正影响到 物理内存 时才报错.
页表是有权限的,所以当我们在进行修改一个常量字符串的时候,虚拟地址必须经过页表映射找到对应的物理内存,但在页表开始对应的时候发现其权限是只读此时你要对其进行修改就会在MMU内部触发硬件错误,操作系统在识别到是哪一个进程导致的之后,就会给该进程发送信号对其进行终止。
(三) 线程总结
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
注释:
计算密集型:执行流的大部分任务,主要以计算为主。比如加密解密、大数据查找等。
IO密集型:执行流的大部分任务,主要以IO为主。比如刷磁盘、访问数据库、访问网络等。
线程的缺点
- 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制:
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 - 编程难度提高:
编写与调试一个多线程程序比单线程程序困难得多。
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)