计算机操作系统之进程与线程
🍋1.什么是操作系统?
操作系统本质上是一个软件,配置在硬件上的第一个软件,可以管理软件和硬件,使其可以有条不紊地运行。
对于操作系统下层,操作系统需要管理好硬件,对于操作系统上层要给软件提供稳定的运行环境,所以操作系统是一个极其重要也极其复杂的“软件”。
对于我们程序员,最重要的是要知道操作系统是如何对进程进行管理,那么问题来了,进程是一个什么东东?
🍋2.进程与线程
🍒2.1 进程
简单说进程就是正在运行的程序,通过双击可执行文件.exe
文件,操作系统就会把这个可执行文件加载到内存上,并在 CPU 上执行可执行文件中的指令,这样这个exe
就跑起来了,就是一个正在运行的程序,也称作一个进程,或者一个任务。
目前我们的电脑都是多进程模式,宏观上,可以有很多个进程同时执行,打开我们电脑的任务管理器,就能查看到我们电脑的进程。
其中有一部分进程需要我们主动的去执行应用的可执行文件,比如 QQ,WPS,网易云音乐等,还有一部分是操作系统自动启动的进程。
🍒2.2 线程
知道了进程就是运行的程序,那线程又是什么?线程是进程内部的一部分,如果说进程是一个工厂,那么线程就是工厂中的生产线,所以一个进程可以包括很多个线程,一个进程至少拥有一个线程。
咱们所写的 java 代码,最终都是通过 java 进程(jvm)跑起来的。
🍒2.3 进程的管理
操作系统是如何对进程进行管理的呢?首先,需要描述一个线程(明确线程的一些属性,操作系统中是通过 C/C++来实现的),对于 Linux 系统是使用结构体来描述进程的,这个结构体也被称为进程控制块(PCB),对进程进行描述后,然后就是组织线程,线程的本质上也是数据,那么对数据的组织需要通过数据结构来进行组织,在 Linux 系统中是通过双链表来组织线程。所以对线程的增删查改本质上就是对双链表进行增删查改,当然这个双链表是复杂的双链表,但是对线程的操作的基本思路和操作双链表是一样的。
🍇2.3.1PCB 中的一些属性
🍉进程 id(pid):进程的一个“身份证号”,进行身份标识。
🍉内存指针:指明该进程的代码或指令的内存在哪个地方,以及该进程所依赖的数据在哪里。
🍉文件描述符表:程序运行中,离不开和文件打交道,进程每打开一次文件,就会在文件描述表上记录一些重要的信息,这个文件描述符表可以视为一个结构体数组,每一个结构体对应一个信息,这个文件描述符数组的下标就是文件描述符。
一个进程一运行,操作系统就会自动打开至少 3 个文件:标准输入(System.in),标准输出(System.out),标准错误(System.err)。
想要一个进程正常工作就需要为这个进程分配资源,包括但不限于内存,硬盘,CPU。
🍇2.3.2 并行与并发
并行性和并发性是既相似又有区别的两个概念。并行性是指两个或多个事件在同一时刻发生。而并发性是指两个或多个事件在同一时间间隔内发生。
从宏观上来讲,并行与并发没有区别,或者说根本就看不出来。从微观上来讲,并行表示两个 CPU 核心运行两个任务的代码,并发表示一个 CPU 核心先运行任务 A 的代码,再运行任务 B 的代码,然后再运行任务 A 的代码,再运行任务 B 的代码,以此类推。只要运行得足够快,就是两个任务在同时运行。
🍇2.3.3 进程的调度
其实调度的最小单位不是进程,而是线程,但为了方便描述进程的调度,我们假定此处的进程只有一个线程,这样就可以将线程的调度视为进程的调度,这里我们谈到的进程调度是基于多任务操作系统,就是从宏观上看,同一时间能够同时运行多个进程。
操作系统对线程的调度可以理解为“时间管理大师”。比如有三个进程 A,B,C,不妨将操作系统比作一个完美的小姐姐,将线程 A,B,C 比作三位小哥哥,A 特别帅,B 特别有钱,C 特别会舔。
这位小姐姐特别爱说话,爱交际,爱谈恋爱,但是这个世界上并没有绝对完美的小哥哥,小姐姐为了体验到“一场完美的恋爱”,同时与 A, B, C 一起谈恋爱,毕竟在这位小姐姐眼中,帅+有钱+会舔的小哥哥是世界上最完美的。
要同时谈三位男朋友,那么这三位男朋友一定不能见面。所以小姐姐需要严密地安排与三位小哥哥谈恋爱的时间,确保时间不能重合,比如:因为小姐姐特别喜欢帅的,其次是有钱的,最后是会舔的,所以小姐姐对 A, B, C 的安排有了优先级,给 A 安排的时间多一点,B 次之,C 最后。这位小姐姐的一周时间安排表,堪称时间管理大师:
像这样的一个时间安排,就是进程的调度。谈恋爱期间,如果 B 需要出差一个月,那么在小姐姐眼中,B 这种状态就称为阻塞或睡眠状态,如果正常,男朋友可以随叫随到,则在小姐姐眼中就称这种状态为就绪状态,小姐姐正在与 A 谈恋爱的状态称为执行状态。
因为这位小姐姐与很多位小哥哥同时谈恋爱,总会有时候“串戏”,就比如 A 需要小姐姐准备礼物庆祝节日,B 想要带小姐姐去夏威夷旅游,要小姐姐做好准备,但是有一天 B 问小姐姐准备好没?小姐姐回了一句礼物早就准备好了,这样就“串戏”了,小姐姐花了很长的精力才将 B 忽悠过去,经历了这件事之后,小姐姐吸取了教训,准备好一个记事本来记录与男朋友们重要的信息,这样,小姐姐从来就没有串过戏。
像上面小姐姐所用的记事本发挥的作用就是进程调度中的记账信息的作用。
🍓进程的状态: 如果进程处于随时都能被调度的状态,就称这个状态为就绪,如果进程由于一系列因素无法及时响应调度,则称这个状态为阻塞或睡眠,线程正在 CPU 上执行的状态称为执行状态。
🍓进程的优先级: 就是类似与上面时间管理大师所认为的优先级,比如 A 进程最重要,那么该进程优先级最高,执行时优先执行 A 进程。
🍓记账信息: 统计每个进程,分别执行了多久,分别执行了哪些指令,分别排队等待了多久时间,可以给进程调度提供指导依据。
🍓上下文: 表示上一次进程被调度出的时候,程序的执行状态,这样下一次进程调 CPU 时就能接着上次的状态继续运行,所以进程调度出之前,都需要先把 CPU 上寄存器上的数据保存到内存中,相当于存档,下次该进程再被调进 CPU 时,就可以根据上次储存在内存中的数据恢复到寄存器中。
进程的调度其实是操作系统对 CPU 资源分配,那内存资源该如何分配呢?
🍇2.3.4 进程间通信
操作系统上同时运行着多个进程,我们来考虑一种情况,就是当一个进程出现 bug 导致进程崩溃,其他进程会不会受影响呢?对于现代的操作系统,是不会影响的。因为线程之间通过虚拟地址空间,来保证进程之间的独立性。
每个进程只能访问自己的那一块虚拟地址空间的数据,无法访问其他进程虚拟地址空间的数据,这样当一个进程崩溃时,另外的进程不会受到影响,这样就将各个进程隔离起来了。
但是,在实际工作开发中,进程间常常需要进行交互,为了实现进程间的通信,操作系统提供了一块“公共空间”,进程 A 可以先把数据存入“公共空间”,然后进程 B 可以到“公共空间”取出数据,这样就实现了进程间的通信。
操作系统提供的“公共空间”有很多种,有储存大的也有小的,有访问速度快的也有慢的,现在最常见的进程间通信机制有:
文件操作。
网络操作(socket)。
那么为什么要引出进程这样一个概念呢?因为现代的操作系统都是多任务操作系统,为了最大效率地运行程序,我们需要进行并发编程,毕竟一群人完成任务的速度比一个人要快的多。
🍇2.3.5 并发编程
通过多进程是完全可以实现并发编程的,但是存在一些小问题,问题根源在于进程的创建需要分配资源,进程的销毁需要回收资源,而分配和回收资源的效率相对来说是比较低的,成本是比较高的。
打个比方,你从网上组装了一台电脑,收到货后发现电脑有点小问题,和商家商量后,最终决定退货,退完货后,老板需要将零件拆开,放回到原位,这个过程需要花费的时间和精力成本是比较高的,而资源的分配与回收也是如此。
🍓那么如何解决这个问题呢?方案 A: 使用进程池,与字符串常量池类似,将已经创建的进程存入常量池,后面需要使用时直接加载即可,但是也存在问题,那就是闲置的进程也会占用资源,相当于空间换时间,消耗空间来提升效率。
方案 B: 使用多线程来实现并发编程,因为进程包含线程,一个进程中包含多个线程,所以线程比进程轻量,因此 Linux 中也将线程称为轻量级进程(LWP),正是因为线程比进程轻量的多,线程的创建与销毁的成本很低,因此使用多线程实现并发编程比多进程更加合适。
打个小比方,进程相当于一个工厂,线程相当于工厂中的生产线,比如有一个任务需要生产 1 万台手机,有两个方案:
建造两个工厂进行生产。
建造一个工厂,多增加一批生产线。
上面两种方案都以相同的效率生产手机,但是方案 1 需要多建造一个工厂,方案 2 只需要增加一批生产线,很明显方案 1 付出的代价大的多,而方案 1 表示多进程实现并发编程,方案 2 表示多线程实现并发编程。
🍇2.3.6 进程与线程的区别(面试高频)
🍓进程与线程的区别(面试高频题):
[x] 进程包含线程,一个进程可以只有一个线程,也可以拥有多个线程。
[x] 进程间是独立的,每个进程都有独立的虚拟地址空间,一个进程崩溃不会影响其余的进程,但是在同一个进程中,多个线程是共用一块资源,一个线程崩溃,这个进程中的所有线程都会崩溃。
[x] 进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位。
好了,本篇博客内容就到这里了,下一篇文章将为大家带来 Java 多线程,下期再见!
版权声明: 本文为 InfoQ 作者【未见花闻】的原创文章。
原文链接:【http://xie.infoq.cn/article/1926ba3d1bf4a6ef7c3e840fb】。文章转载请联系作者。
评论