进程的定义
进程就是程序动态运行的实例,它是承担分配系统资源(CPU时间,内存)的实体。我们也可以把进程当成是由一组元素组成的实体,进程的两个基本的元素时程序代码和与代码相关联的数据集合。在进程执行时,都可以被表征为一下元素:
- 标识符:与进程相关的唯一标识符,用来区别正在执行的进程和其他进程。
- 状态:描述进程的状态,因为进程有挂起,阻塞,运行等好几个状态,所以都有个标识符来记录进程的执行状态。
- 优先级:如果有好几个进程正在执行,就涉及到进程被执行的先后顺序的问题,这和进程优先级这个标识符有关。
- 程序计数器:程序中即将被执行的下一条指令的地址。
- 内存指针:程序代码和进程相关数据的指针。
- 上下文数据:进程执行时处理器的寄存器中的数据。
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表等。
- 记账信息:包括处理器的时间总和,记账号等等。
操作系统对进程的控制就是通过对上面的这些元素的控制来控制操作系统的,但这些信息都不是单独存放的,而是存放在一个叫做PCB(进程控制块)的结构体中,这个结构体由操作系统创建和管理。
线程的定义
- 在现代操作系统中,进程支持多线程
- 进程是资源管理的最小单元;
- 线程是程序执行的最小单元。(单进程是有个主线程(main函数)所在的线程)
如果你的计算机有4个 CPU 那么,现有一个进程中的四个线程可以运行在每一个CPU上的,能做到真正意义上的并发。
- 线程作为调度和分配的基本单位
- 进程作为资源分配的基本单位
一个进程的组成实体可以分为两大部分:线程集和资源集。进程中的线程是动态的对象;代表了进程指令的执行。资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。
线程的概念
概念
- 线程是一个进程内部的控制序列。控制序列可以理解为一个执行流。进程内部是指虚拟地址空间
- 一个进程至少有一个线程(主线程)。
进程到线程
- 进程: 承担分配系统资源的实体
- 线程:共享进程所获得资源
进程 VS 线程
创建和销毁进程、线程耗时对比
单纯创建进程线程(10万次): - |
进程 | 线程 |
---|---|---|
real time | 18.775s | 9.212s |
real time:实际使用时间
调用exit对于主进程的影响
- 多线程,某个线程异常,能够导致所在进程直接挂掉;
- 多进程,某个进程异常,不会影响其他进程的执行;
为什么引入线程
在看引入线程是为了解决什么问题之前,我们先来了解个概念 - 虚拟内存:
以 32位系统来讲,它的寻址空间是:0~4G(2^32);虚拟内存认为全世界都是自己,每个进程都支持0~4G的寻址(即每个进程都觉得这个4G内存都是归它自己用的。)
如果你的操作系统支持和 CPU 支持虚拟内存
MMU:内存映射单元
所有的进程都是公用同一块物理内存,物理内存真正加载数据。当 CPU 执行到某一区域(0x1fff ffff)发现没有在内存,就从磁盘把该区域的指令加载到物理内存(0x0001 ffff),然后通过 MMU 建立映射关系(0x1fff ffff->0x0001 ffff)。
如果物理内存满了,CPU 会把长时间不用的数据置换到硬盘,让出内存给别的进程使用。
我们编写程序的时候也没有考虑内存重叠的事,因为映射到物理内存的时候都是kennel内核管理的
多进程的目的之一是为了提供并发能力,但进程的创建需要新分配虚拟地址空间、页表、物理内存等等?那么我们是否可以创建轻量级的并发实体,共享进程的资源?
并发实体尽可能去共享进程的资源,比如共享一块地址空间。共享一个页表和一块物理内存。
图中,有3个PCB,在以前的认知里,我们叫做3个进程。
线程私有资源
线程共享的环境包括:
进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
每个线程私有的资源包括:
- 线程ID
- 寄存器组的值
- 线程的栈
- 错误返回码 errno值
- 线程的信号屏蔽码
- 线程的优先级
线程的实现方式
- LWP(轻量级进程)作为多线程方案
- 纯用户空间多线程方案 (协程方案)
- 混合版多线程方案(前两者的混合版本)
LWP作为多线程方案
LWP
是轻量级进程,在 fork() 的时候参数不一样
优点:
- 真正实现并行操作(每个线程都可以独立运行,其中某个线程阻塞,不影响其他线程的运行)
- 克服阻塞问题
缺点:
- 控制转移开销大
- 调度算法由操作系统核心确定,应用进程无法影响线程的切换数量存在限制
纯用户空间多线程方案
U: 纯用户空间线程即协程。
多个协程对应一个内核调度实体。如果某一个 协程 阻塞了比如:sleep(sleep会让出CPU),其他的 协程 就不能运行了。
优点:
- 切换速度快;
- 调度算法可专用;
- 可运行在任何操作系统上(汇编处理)
缺点:
- 阻塞问题;
- 多处理器利用问题。
多线程问题
1.阻塞的问题
- 多线程某个线程阻塞,不会影响其他线程;
- 协程某个协程阻塞,会影响其他协程->调用系统调用的时候,尽量不要以阻塞的方式来调用。send,recv都要以非阻塞的方式进行
协程如果要 sleep,要调用协程框架的函数,不能直接调用系统调用的sleep()
。得用协程框架提供的接口,比如 st_thread
st_usleep
。即协程得自己在用户态实现调度算法。
协程好处:协程可以做到以同步的方式编写代码。
2.调度的问题
- 用户态到用户态的切换切换: 非常快
- 用户态到内核态的切换是比较耗cpu
线程的调度,涉及到 内核-用户的切换是非常耗资源的(线程之间的切换,如果线程开的太多,影响就会很明显)
无论是多进程还是多线程,只要数量一多,效率肯定上不去。CPU 为每个线程分配的时间片是固定的,线程太多后,线程和线程就会竞争资源。
多线程和多协程的使用场景
- 多线程: 能够充分利用 CPU,某个线程阻塞也不影响其他线程,比较适合密集型计算。
- 多协程: 用户态的切换,开100万协程都是可以,比较适合 IO 密集型。
我们可以把任务分为计算密集型和IO密集型。
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,可以使用多线程:能够充分利用 CPU,某个线程阻塞也不影响其他线程。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,多个连接频繁切换,因此使用用户态间切换的多协程就比较适合。