【线程基础】进程和线程
一.进程
进程就是操作系统提供的一种“软件资源”。我们现在所用的系统,都属于是“多任务操作系统”,就是说同一时刻,可以运行多个任务/进程。每个任务在执行过程中,都需要消耗一定的硬件资源。进程是系统分配资源的基本单位。
1.操作系统的进程管理
- 先描述(使用类/结构体这样的方式实现,把实体属性表示出来)
表示进程信息的结构体,叫做PCB(进程控制块,Process Control Block)。 - 再组织(使用一定的数据结构,把这些结构体/对象串到一起)
- 当我们看到任务管理器中的这些进程的时候,意味着系统内部就在遍历链表,并且打印每个节点的相关信息。
- 如果运行一个新的程序,系统中就会多一个进程。多的这个进程就需要构造出一个新的pcb,并且添加到链表上。
- 如果关掉一个程序,就需要把对应进程的pcb从链表中删除,并且销毁掉对应的pcb资源。
事实上使用的数据结构不是链表,而是一个更复杂的链式结构。
2.PCB的核心属性
属性 | 功能 |
---|---|
pid | pid是进程的身份标识(同一机器同一时刻不重复) |
内存指针(一组) | 描述这个进程能使用哪些内存(指令和数据),每个进程只能使用自己申请的内存 |
文件描述符表 | 描述了进程所涉及的硬盘相关资源 |
CPU | 描述该进程所消耗的时间占CPU总运行时间的比例 |
状态 | 描述某个进程是否能够去CPU上执行(就绪状态、阻塞状态(比如说加锁、等待用户输入)) |
优先级 | 多个进程等待系统调度,调度的先后关系(先后、时长) |
记账信息 | 统计每一轮各个进程占用CPU的时间,根据统计结果来调整下一轮次CPU的分配,防止某个进程完全分配不到CPU |
上下文 | 支撑进程调度的重要属性,相当于游戏中的存档和读档 |
- 存档:在当前进程被调度出CPU之前,把当前寄存器中的这些信息,单独保存到一个地方。
- 读档:在该进程下次再去CPU上执行的时候,再把这些寄存器的信息给恢复回来。
- 操作系统将硬盘这样的设备,封装成了文件。一个进程要想操作文件,需要先“打开文件”,就是让进程在文件描述符表中分配一个表项,来表示这个文件的相关信息。
2.1 CPU分配——进程调度(Process Scheduling)
如何理解PCB中的CPU属性?
前面说到,CPU就是描述执行该进程的时间占CPU运行时间的比例。
CPU有数十个核心,每个核心都可以单独执行一个进程。但我们的电脑上不止数十个进程,那就意味着如果每个进程霸占一个核心,就会导致核心不够用的情况。那么CPU是如何解决这个问题的呢?
首先要清楚一个概念,在同一时刻一个核心只可以执行一个进程;但在不同时刻,同一核心就能执行不同的进程,也就是说在不同时刻可以执行不同的进程。
那可以先执行完一个进程,再执行别的进程吗,这显然也是不合理的。如果这样,一个电脑上不能同时存在 超过逻辑核心数 的进程,然而我们的电脑上有成百上千个进程,所以只能是进行分时复用。也就是并发执行:
- 先讨论单个核心的情况:
让这个核心先执行进程1的代码,执行一会儿之后,让进程1下来,再执行进程2的代码,进程2执行一段时间之后,让进程2下来,再让进程1上。每次执行都不一定能将该进程的指令执行完。
只要切换速度够快,人是感知不到切换的过程的。也就是说,在人肉眼看来,多个进程是在同时执行的。这就是并发。 - 现有的CPU都是多核的:
在单个核心并发处理多个进程的同时,多个核心还可以并行执行多个进程。 - 并行和并发同时存在,统称为并发。
所以说,PCB中的CPU属性,是执行时间占比。
2.2 内存分配——内存管理
不同进程使⽤内存中的不同区域,互相之间不会⼲扰。
2.3 进程间通信
现代的应⽤,要完成⼀个复杂的业务需求,往往⽆法通过⼀个进程独⽴完成,总是需要进程和进程进⾏配合。
所谓进程间的通信就是说:系统提供一些公共的空间(多个进程都可以访问到),让两个进程借助这种公共空间来交互数据。
目前主流操作系统提供的进程通信机制如下:
- 管道
- 共享内存
- ⽂件
- ⽹络
- 信号量
- 信号
为什么Java不鼓励多进程编程?
原因也是Java运行在JVM虚拟机上,如果要进行多进程编程,就需要启动多个JVM虚拟机,这显然是比较浪费资源的,而相对来说,多线程可以在一个JVM虚拟机上一起执行,所以Java中多线程编程运用的还是比较多的。
二.线程
有些场景下,多进程的表现不尽人意。具体就表现在需要频繁创建和销毁进程的时候。进程是系统分配资源的基本单位。频繁地创建和销毁进程就需要频繁地从内存中加载代码和数据。取指令的操作,相对于CPU执行计算,非常耗时、开销很大,主要体现在资源的申请和释放上。(这也是CPU缓存、流水线的原因)。
那线程是如何改进这一点的呢?
线程可以看作为“轻量级进程”。在进程的基础上,既保持了独立调度执行,又省去了“分配资源”、“释放资源”带来的额外开销。相对于进程,线程做到了资源共享,即多个线程共用一份内存空间(一个进程可以包含多个线程,进程是系统资源分配的基本单位)。当某个线程销毁的时候,也不会立即释放这块内存空间。
线程也使用PCB来描述,PCB中有一个重要属性,即内存指针。多个进程的内存空间不共用,多个线程(同一进程)之间的内存空间可以共用。
除了内存空间,多个线程还可以共享一份硬盘空间。
举个形象的例子,以吃饭为例:
在这个例子中,我们简单的将桌子理解为进程,将人理解为线程。
面对同样一个任务,多进程需要申请多份内存空间和文件描述符表(硬盘),就相当于吃饭时给每个人支了一张桌子。
而多线程就是在共用一份内存空间和文件描述符表(硬盘),就相当于多个人围着一张桌子吃饭。
这样,我们很容易理解多进程对于频繁申请和释放资源的弊端。
那么多线程就完全优于多进程了吗?
这件事也未必,多个线程由于共用一份内存资源,容易引起线程安全问题。
三.小结
- 进程是包含线程的
- 每个线程,都是一个独立的执行流,单独的参与到CPU的调度中(线程有自己的PCB)
- 每个进程,有自己的资源,进程中的线程可以共用一份资源(内存空间、文件描述符表)
进程是资源分配的基本单位,线程是调度执行的基本单位 - 进程和进程之间不会相互影响,如果同一个进程中的某个线程异常,可能会影响同进程的其他线程。(线程安全问题)
- 线程也不是越多越好,线程太多,调度开销会明显增大。