前言:
本文将对进程与线程之间的关系、各自运作做简单介绍,介绍背景基于Linux系统,因为Windows、mac不开源,无法得知内部运作
一、进程
1.什么是进程(Process)/任务(Tast)
进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程; 同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
2.进程控制块抽象(PCB Process Control Block)
1)PCB基本介绍
计算机内部要管理任何现实事物,都需要将其抽象成一组有关联的、互为一体的数据。在 Java 语言中,我们可以通过类/对象来描述这一特征。C语言使用结构体
// 以下代码是 Java 代码的伪码形式,重在说明,无法直接运行
class PCB {
// 进程的唯一标识 —— pid;
// 进程关联的程序信息,例如哪个程序,加载到内存中的区域等
// 分配给该资源使用的各个资源
// 进度调度信息
}
所以基于以上:(Linux系统下)
所谓“创建线程”,就是先创建出PCB ,然后把PCB加到双向链表
所谓“销毁线程”,就是找出链表上的PCB,并且从链表上删除
所谓“查看任务管理器”就是遍历链表
2)PCB属性
- pid(进程Id) :作为进程的身份标识
- 内存指针:指明这个进程要执行的代码/指令的内存在哪里,以及这个进程执行中依赖的数据都在哪里
- 文件描述符表:
- 程序运行过程中,经常要和文件打交道(文件是在硬盘上面的)
- 进程每次打开一个文件,就会在文件描述符多增加一项(这个文件描述符就可以视为一个数组,里面的每一个元素又是一个结构体,对应一个文件的相关信息)
- 一个进程只要一启动,不管代码中是否带有打开/操作文件的代码,都会默认的打开三个文件(系统默认打开),分别是标准输入(System.in)、标准输出(System.out)、标准错误(System.err)
3.CPU 分配 —— 进程调度(Process Scheduling)
操作系统对CPU资源的分配,采用的是时间模式 —— 不同的进程在不同的时间段去使用 CPU 资源。
对于多个进程同时运行,操作系统使用的是“并发编程”,这个“并发编程”是宏观上的,微观上是两个不一样的东西,分别是【并发】与【并行】
【并行】微观上,两个CPU核心,同时执行两个任务
【并发】微观上,一个CPU核心轮流执行多个任务
并行与并发都是微观上,宏观上,人类感知不到微妙的时间差,所以宏观上我们都叫做“并发”
- 状态:可运行状态:可运行状态表示的进程,可能没有运行,也可能已经在运行
阻塞状态/睡眠状态:暂时不用上CPU运行- 优先级:优先给谁分配运行时间,其次再给谁分配时间,分配多少
- 记账信息:统计了每个进程,都分别执行了多久,都执行了哪些命令,分别排队多少,是用来给进程调度提供指导依据
- 上下文:简单解释就是【存档+读档】,进程在调度的时候也是一样,进程很可能执行了某个操作,执行一半,就被调度走了,过一段时间,进程还要回来从上次执行到的位置继续往下执行!对于进程来说,上下文,具体指的就是 CPU 里的一堆寄存器里面的值。上下文就会在进程被切出 CPU 的时候,把寄存器的状态保存到 PCB 里(内存)。下次进程回到 CPU 上就把 PCB 里的上下文读取出来,恢复到 CPU 寄存器中。
4.内存分配 —— 内存管理(Memory Manage)
相比于直接分配内存地址给进程,虚拟地址并不存在于实际内存中,而是假象于进程本身中,然后通过MMU做映射到实际物理内存的工作。
虚拟内存地址给进程造成一种独享内存的错觉,内容访问也在仅在自己的虚拟内存范围内,即使内容指针出错也不会指到隔壁进程去,也就是将问题限制于自身内部。假如内容指针出错成了野指针跑到MMU中进行映射工作,也会被MMU发现,并告知指针出错
【虚拟内存过大问题】
- 虚拟内存虽然很大,但是实际上使用到的物理内存很小
- 极端情况下,内存被吃满了,这种情况要么是bug,要么是确实有进程要使用这么多的内存。对于这种情况,要么检测bug要么增加内存。
5.进程间通信(Inter Process Communication)
由于在资源分配的时候,就注定了两个进程之间具有隔离性,但是不同进程之间也需要进行互动交换信息。所以进程间的通信就此而生
目前主流的操作系统下的通信机制:
- 管道
- 共享内存
- 文件
- 网络
- 信号量
- 信号
其中,网络是一种相对特殊的 IPC 机制,它除了支持同主机两个进程间通信,还支持同一网络内部非同一主机上的进程间进行通信。
二、线程
1.线程是什么
一个线程就是一个 " 执行流 ". 每个线程之间都可以按照顺讯执行自己的代码 . 多个线程之间 " 同时 " 执行着多份代码.
举个例子:银行的每个服务窗口就是一个线程(银行本身就是进程)
2.为什么要有线程
- 首先, "并发编程" 成为 "刚需".
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
- 其次, 虽然多进程也能实现并发编程,但线程比进程更轻量.
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
- 最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"(Coroutine)
补充说明:
- 进程的资源申请和释放是比较低效的,线程之所以轻,在于资源用完不用释放,需要也就直接用,就相当于,进程一次的部署,全家终身收益。
- 对于进程资源的申请和释放问题,虽然可以使用进程池预先准备好,但是在闲置的时候,依旧会占用大量资源
3.进程和线程的区别
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间。
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
实际例子就是一个工厂内有多个流水线,多个流水线能更好的完成任务,但也不能过多,造成拥挤抢占资源变得低效,所谓物极必反。
4.Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制 , 并且对用户层提供了一些 API 供用户使用( 例如 Linux 的 pthread 库 ).Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装 .
三、线程与进程面试问题
谈谈进程与线程的区别与联系
- 进程包含线程,一个进程里可以有一个线程,也可以有多个线程
- 进程和线程都是为了处理并发编程这样的场景,但是进程在频繁的创建和释放的时候比较低效率。相比之下,线程更轻量,少了申请和释放这个过程,是直接使用进程内部已有的资源,一个进程内部的多个线程共享一个内存、文件等资源
- 操作系统创建进程,要给进程分配资源,进程是操作系统分配资源的最小单位。操作系统创建线程,在CPU上调度,线程是操作系统调度执行的基本单位。
- 进程具有独立性,每个进程有各自的虚拟地址空间,一个进程挂了,不会影响其他进程,同一个进程中有一个或者多个线程,它们共用一个内存空间,一个线程挂掉,可能影响到其他线程,甚至整个进程崩溃