目录
进程
什么是进程?
进程(Process)是操作系统提供的一种 "软件资源",是 操作系统 对一个 正在运行的程序 的一种抽象,即,可以把 进程看做程序的一次运行过程,进程依赖于程序运行而存在,进程是动态的,程序是静态的
当我们打开任务管理器,就可以看到:
每个进程在执行过程中,都需要消耗一定的硬件资源,因此,计算机中的每个进程在运行的时候,都需要为其分配一定的资源空间, 也就是说,在操作系统内部,进程是系统分配资源的基本单位
进程在系统中是如何管理的?
要想管理系统中的进程,首先需要对进程进行描述,每个进程在操作系统中都有一个对应的 进程控制块(PCB),PCB 是操作系统维护的数据结构,用于存储进程的状态信息和控制信息
然后再将系统中的进程组织起来,操作系统使用数据结构(如 线性表、搜索数等)将 PCB 组织起来,方便管理时进行增删查改
PCB(进程控制块)
PCB(Process Control Block)是实现进程管理的核心数据结构,PCB中包含了上百个属性(进程的标识符、状态(如运行、就绪、等待)、程序计数器(指向下一条将执行的指令)、内存指针(指向进程的地址空间)、上下文信息(包括寄存器的状态)、进程优先级、所属用户等信息...),接下来我们就来了解一些常见的属性
PID
PID(Process Identifier)即进程标识符,是进程的身份标识,用来标识操作系统中的每一个进程。每一个进程都有一个唯一的 PID,操作系统通过 PID 来识别和管理不同的进程
内存指针
内存指针,用于描述进程使用内存资源的详细情况,在进程运行过程中,需要消耗一定的系统资源,其中,内存就是一种重要的资源,在进程运行前,需要向操作系统申请一块内存空间,而内存指针,就是用来描述当前进程都能使用哪些内存、哪些部分存放的是指令(代码),哪些部分存放的是数据
文件描述符表
在进程运行过程中,通常需要访问硬盘,操作系统将硬盘这样的硬件设备封装成了文件,因此,我们只需要统一使用 文件 的方式来进行操作
每个进程都有一个固定大小的文件描述符表,通常是一个数组或者类似数据结构。每个表项存储了与打开文件相关的信息,如文件指针、文件状态标志(如读、写、追加等)、文件打开模式(只读、只写、读写等)
状态
进程在操作系统中可以处于不同的状态,这些状态反映了进程在其生命周期中的不同情况和条件,如就绪状态、阻塞状态
优先级
优先级描述的是:在多个进程等待系统调度的过程中,多个进程之间调度的先后关系
记账信息
针对每个进程占用了多少 CPU 时间进行统计,然后根据统计结果来进一步调整调度的策略
上下文
上下文是支撑进程调度的重要属性,相当于 存档 和 读档
每个进程在运行过程中,会有很多的中间结果,存储在 CPU 的寄存器中,当操作系统在多个任务之间切换时,会保存和恢复每个任务的状态,这一过程称为上下文切换。上下文切换涉保存上下文和恢复上下文
多个任务的切换,就涉及到 进程调度
进程调度
调度 是指操作系统根据一定的调度算法,决定将CPU分配给哪个进程执行的过程
CPU(中央处理器)是计算机的核心组件,负责执行程序指令和处理数据,承担了所有的计算任务,但 单个 CPU 一次只能执行一个任务,也就是运行一个进程,其他进程处于非运行状态,若出现操作系统需要同时执行多个任务的情况,这时候应该怎么办呢?
这就涉及到了 分时复用,并发执行
CPU 核心只有一个,因此,可以先执行 进程1 的代码,执行一会后,让 进程2 执行,进程2 执行一段时间后,再让 进程3 执行...
只要切换速度足够快,人是感知不到这个切换过程的,因此,在我们看来,多个进程就是 "同时执行" 的
CPU 的频率一般都是以 GHZ 为单位,也就是说,CPU 一秒钟能够执行几十亿条指令,这也就意味着:在短时间内,CPU 可以进行很多次任务的切换,只要切换速度足够快,人就感知不到,但是,若系统进程太多时,CPU 负担太重,就会出现 卡顿 的情况
现代的 CPU 通常都是 多核 的,也就意味着可以同时执行多个进程(如,一个 4 核 CPU,就可以同时运行 4 个进程),此时,不同核心上的CPU 就是同时执行的,而不是靠快速切换模拟的 同时执行,也就是 并行执行
但由于 同一时间 运行的任务可能超过 CPU 的核心数,因此,并发执行仍然存在,每个核心仍然需要分时复用
多个进程是并行执行还是并发执行,都需要看系统的调度,而系统如何进行调度,取决于系统模拟器模块是如何实现的,我们一般都感知不到,因此,往往把 并行 和 并发,通称为 并发
了解了 进程调度 之后,我们就能进一步理解 上下文,当 进程1 执行一段时间后,此时需要执行 进程2 了,就需要在 进程1 调度出 CPU 之前,将当前寄存器中的数据存储起来,也就是 保存上下文;而当下一次调度 进程1 时,需要继续执行 进程1 的代码,就需要将 上次存储的数据 加载到 CPU 的对应寄存器中,也就是 恢复上下文
进程间通信
进程是操作系统进行资源分配的最小单位,这就意味着各个进程相互之间是无法感受到对方的存在的,因此,进程之间相互具有 "隔离性"
虽然进程具有独立性,但在有些场景,多个进程可能需要相互通信和协作,例如数据共享、消息传递等。操作系统提供了多种进程间通信(IPC)的机制,如管道、信号量、消息队列、共享内存等。进程间通信的实现可以通过操作系统提供的系统调用或特定的API来完成,确保进程之间可以安全地交换信息和协作执行任务。
在了解了 进程 之后,我们来学习 线程 相关知识
线程
为什么需要使用线程?
多任务操作系统,当然希望系统能够同时运行多个程序(若是 单任务的系统,也就不涉及进程,不需要管理,更不需要调度),因此,从本质上来说,进程解决的是 "并发编程" 的问题
进程可以很好的解决 并发编程 这样的问题,但某些特定的情况,例如,需要频繁创建和销毁进程时,此时使用 多进程编程,系统开销就会很大(开销主要体现在 资源申请 和 资源释放 上)
为了解决上述问题,就引入了 多线程
什么是线程?
线程,也可以称为 轻量级进程,在进程的基础上做出了改进,保持了 独立调度执行 这样的 并发支持,同时省去了 分配资源、释放资源 带来的额外开销
多线程是如何做到的呢?
在前面我们学习了使用 PCB 来描述一个进程,一个进程有一个PCB,我们来看不同进程分配的内存空间:
线程在系统中的调度规则,和 进程 是一样的,线程的 PCB 中也有 状态、优先级、上下文、记账信息...,我们来看不同线程分配的内存空间:
PCB 中有一个重要的属性——内存指针(描述进程使用内存资源的详细情况),多个线程的PCB 中的内存指针,指向的是同一个内存空间,这也就意味着,只需要在创建第一个线程的时候从系统分配资源,后续的线程,就不必分配,直接共用前面的那份资源,除了内存之外,文件描述符表 也是多个线程共用一份的
任意两个线程都能够实现资源共享吗?
当然不是,我们将能够资源共享的这些线程划分为一组,称为 线程组:
我们可以看到,每个进程,都包含了一个或多个线程,在没有引入线程时,进程是资源分配的基本单位,也是调度执行的基本单位;而有了线程之后,进程是资源分配的基本单位,线程是调度执行的基本单位
我们通过一个例子来进一步理解 线程 和 进程:
假设此时有一个任务需要完成——生产100个玩偶
此时有一个工厂(进程),其中有一个工人(线程)来完成这个任务
此时应该如何提升生产效率呢?
若以多进程的方案,我们可以再找一个新的工厂(创建一个新的进程),来共同完成任务,此时能够提升效率,但成本很高(创建一个新的进程需要申请更多的资源)
而以多线程的方案,仍是原来的工厂,但我们再找一个工人(创建一个线程)来完成这个任务,此时也能够提升效率,且这种方案成本更低(资源开销更小)
若我们让更多的工人来完成这个任务(这些工人共享同一份资源,共同使用工厂的工具进行生产),此时效率就会更高,与之对应的,若我们找更多的工厂,成本就会更高
但是,引入的工人越多越好吗?
当引入工人达到一定数量的时候,再尝试引入新的工人,就没办法提升效率了,因为工厂的生产工具没有那么多,也就是说,当线程数量太多时,线程之间就会相互竞争 CPU 的资源,此时非但不会提高效率,反而还会增加调度的开销
采用多线程能够减少资源开销,但共享资源也有其副作用
若工人A 在拿工具时没拿到,生气了,直接将工厂电闸一拉,此时整个工厂都不能再继续生产了
也就是说,若一个线程抛出了异常,并且没有处理好,就可能导致整个进程被终止
在学习了 进程 和 线程 之后,我们来总结一下 进程 和 线程 之间的关系
线程与进程的关系
1. 进程是包含线程的,每个进程至少有一个线程存在,即主线程
2. 每个线程都是一个独立的执行流,都可以执行代码,且单独参与到 CPU 调度中
3. 每个进程都有自己的资源,进程和进程之间不共享资源,同一个进程的线程之间共享一份资源(内存空间和文件描述符表)
4. 进程是资源分配的基本单位,线程是调度执行的基本单位
5. 进程和进程之间互不影响,同一个进程中的某个线程抛出异常,可能会影响到其他线程
6. 同一个进程中的线程之间可能会互相干扰,引起线程安全问题
7. 线程也不是越多越好,若线程太多,调度开销可能会非常大