什么是线程?
常见的概念
线程是比进程更加轻量级的一种执行流线程是进程内部执行的一种执行流
- 线程是CPU调度的基本单位
- 进程是承担系统系统资源的基本实体
下面来解释这些概念
线程
进程=内核数据结构+代码和数据
一个进程的创建伴随 PCB创建,进程地址空间的创建,页表的映射。同时还会加载动态库。
然后将进程PCB链接到调度队列中,等待被调度。
每个进程都有独立的地址空间和页表,还有一套独立的寄存器。因此进程是互相独立的。
为了减少“成本”,子进程我们只创建PCB,将PCB链接到同一份地址空间后。并且通过方法,将正文代码分发给不同的“子进程”执行,对此原本的代码就被分发成“子进程”的执行。我们这里的子进程实际上就是线程!
对此每一个线程都是一个执行流。线程是进程内部的执行流!
重新定义进程
现在我们将所有的PCB、进程地址空间、页表统一叫做进程。
之前我们说的进程其实就是单执行流,只有一个线程。当内部有多个执行流时,就是多线程。
进程向系统申请的资源,在内部被线程瓜分了,进程是系统资源分配的基本实体!
时间片也是资源,被进程瓜分。线程再去进程平均分享时间片。
而在Linux中,其实是没有线程这个概念的,因为线程和进程的调度,阻塞的控制块是类似的,换言之,Linux不创建线程专属的方法,沿用进程的方法。一套代码就能实现进程和线程。为此,线程又叫做轻量级进程。
所以在Linux中不识别线程和进程!
CPU只关心执行流,因此线程是CPU调度的基本单位!
如何理解线程比进程的效率更高
- 资源开销小,线程创建只需要分配相应的寄存器和栈,进程还需要分配内存空间,文件等资源。线程相应的寄存器比进程少,跳转少,速度更快。
- 局部性原理,数据交互快。进程之间无法共享内存,CPU的cache会预加载热数据,就是提前加载被访问数据的下文,命中的概率是大的。线程之间不需要重新加载cache寄存器。而进程是不共享数据,每一次切换都需要重新加载cache。
页表的补充
磁盘上的程序应该是被划分成4kb大小的页帧,加载程序时,是4kb的加载。
在物理内存中,应该也被划分成4kb的页框。
磁盘和物理内存的数据交换就是4kb进行的。
如果我们想要修改磁盘上1字节的数据。我们必须将4kb都加载到内存中,修改,写入。
二级页表
虚拟地址到物理地址的转化
一个虚拟地址有32位,这32位并不是全部用来表明地址
虚拟地址被分成 10+10+12
页目录
假设4GB的物理内存
前10位地址作一级页表的索引
需要10个比特位,找到是哪一个页表项(一级页表)
实际上一级页表话包括是否命中,是否可写等
如果s保存的是一个常量字符,在通过虚拟地址到物理地址的映射时,会被标志位不可写
再后续通过页表跳转时,会读取到只有可读权限,如果试图修改常量,OS触发信号终止程序。
页表
中间十位(二级页表索引)找到具体的页框项
通过10位的物理地址,就能找到对应页框的4KB
页内偏移
4KB=1024*4=2^12次方
因此只需要12位bit位就能表示4KB里的每个字节
故访问到物理内存就是通过起始地址+偏移量
我们创建的内置类型 如int 实际上类似就是偏移量的多少。
CPU里面有寄存器保存了页目录的地址。eip寄存器保存了虚拟地址,通过MMU寄存器转化到物理地址。所以我们平常看到虚拟地址,底层看到物理地址。
实际上,加载页表时,也遵循局部性原理。会根据需要加载需要内容附近的页目录。如果不命中,再记载另一块。
缓冲区就是指针指到一块空间上。
缺页中断
软件试图访问已经映射的虚拟地址时,但当前并未被加载到物理内存的页框时。
OS会检测到缺页中断,调取内容,加载到内存上。
当调用完加载后,会继续执行当前指令。
线程的优点
创建线程的成本低:寄存器,地址空间,页表,动态库等
切换开销小:共享内存切换少量寄存器,页表切换开销小,不需要重新分配内存、文件。
线程占的资源更少
充分多核并行
提高相与速度:等待慢速IO时,程序可以执行其它计算任务
计算密集型:将计算分到多个进程
IO密集型:可以等待多个IO操作
线程的缺点
性能损失:线程调度和上下文切换可能会导致性能损失。频繁切换线程会导致性能下降。
健壮性降低:共享了不该共享的变量导致相互影响的可能性很大,需要线程保护。
例如:一个线程异常终止,会导致所有线程终止。(线程异常)
访问缺乏控制:堆,静态区的数据会被所有线程访问到,缺乏控制。
编程难度高
线程和进程的对比
进程是资源分配的基本实体
线程是调度的基本单位
线程共享进程的资源,但也有独立的数据
- 独立的寄存器:线程再运行时会用到各种寄存器,如计数、栈指针。由于线程是并发的,每个线程都必须有独立的寄存器来保持上下文信息
- 独立的栈:函数的返回值,局部信息需要被栈保存,因此有独立的栈。
- 线程ID
- errno
- 信号屏蔽字
- 调度优先级
线程和进程共享
进程的代码段:任何一个线程可以执行进程中的函数
进程的公有资源:全局和静态变量,堆
进程的其它资源:打开的文件,动态库链接。
地址空间、页表。