大家好,我是胡亦,一名热爱分享技术干货的博主。
前几天在复习多线程章节时,越发觉得多线程在未来开发中的重要性了,作为多线程的基础——进程和线程。这两个点的重要性不言而喻,而我本人对于进程和线程的概念都只是停留在表面浅层的理解,比如:进程包括线程,线程就是进程执行的一条路径等等…
于是,为了有更深入的了解,抱着弄不明白誓不罢休的决心,最终发现几篇文章写的还不错,对于理解进程和线程非常有帮助。
所以,在这里分享给大家!
如果你和我一样,对进程和线程的的了解尚浅,或者想了解关于多线程方面的知识,却一直没有好的资料。
那么这篇文章或许可以给你一点启发。
操作系统是对计算机硬件资源的管理程序,是应用程序与计算机硬件交互的中间层,其本质仍旧是运行于硬件电路上的程序。
操作系统可以使我们的硬件读取指令,并且操作指令,所以我们编写的所有的源代码(java代码、mysql代码…),都是面向指定的语法的(因为它们最终都会被解析成指令),最终都要转换为计算机系统可以识别的内容,而计算机系统包括硬件以及运行其上的系统软件。
我们所有的编码,都是面向指定的语法,而这门语言本身,则是面向操作系统的,因为外部软件通常是不能直接操纵硬件资源,需要借助于操作系统。
所以从某种程度上可以这样认为,所有的源代码都是面向语言的,而语言本身面向操作系统的。(因为我们需要源代码与硬件进行交互,产生效果,操作系统就是源码和硬件之间的中介)
程序是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,当你的电脑中安装一个软件后,如果不启动软件,该软件也仅仅是占用磁盘空间。
当程序需要运行时,操作系统会加载该程序的信息到内存中,并且分配CPU时间片以及其他硬件资源并且会对这些资源进行管理,比如数据加载到内存的什么位置了?
而且,现代操作系统都可以同时并发执行多个程序,内存中的这些数据又都是哪个程序的?某个软件在进行切换时执行到哪里了?等等这些都需要操作系统进行管理
操作系统将程序的一次运行抽象为进程
进程是操作系统对程序的一次执行的抽象,也就是说一个程序运行需要哪些信息、数据?这些所有的数据项集合就叫做进程。简言之就是一个程序运行所需信息的描述集合。
public class 进程 {
private Long 进程号;
private String 程序计数器PC;
private String xxx寄存器;
private String 堆栈内容;
// ............................等等
}
什么是进程上下文?
刚才说到现代系统还可以并发的执行多道程序,必然存在着CPU的切换,那么从一个程序切换到另一个程序时,如何才能够恢复?
既然进程是程序的一次运行过程中所需要信息的集合,如果在切换时,将这一瞬时状态,这一集合体各项数据记录下来,当再次切换回来时,只需要将数据恢复不就好了吗?进程上下文就是进程切换的这一瞬时状态时,集合的各项数据记录。
进程执行活动全过程的这一个静态描述叫做进程上下文,进程间的切换,也被称之为上下文切换。
随着现代计算机技术的发展,进程的弊端开始出现
因此就需要引入轻型进程,线程就是轻量级的进程,进程仍然是资源分配的基本单位,线程是进程执行的最小单位;线程的出现可以理解为计算机操作系统对于程序的执行进行了更加精细化的控制,将资源分配,程序运行进行了更加细致的分工。
每个线程都运行在进程的上下文中,共享同样的代码和全局数据,很显然,多线程比多进程更容易共享数据。
总之,线程的出现是操作系统技术的发展,为了更加细化分工,节省开销的一种做法,是在进程的基础上发展而来的。
并发(concurrent ):通过CPU调度算法,进行进程间的切换,也就是多任务执行,操作系统将CPU时间片分配给每个进程,给人并行处理的感觉,其实是一种假的并行
并行(parallel):并行就是同时执行的意思,多个CPU或者多个机器同时执行一段处理逻辑,是真正的同时
很久很久以前,操作系统以串行的方式运行,当正在执行的程序遇到阻塞操作,比如等待IO时,CPU空闲等待,极大的浪费了CPU
所以后来出现了多任务操作系统,可以对程序进行切换,当遇到阻塞操作时,CPU可以去执行另外的程序,提高了CPU的利用率。
对于线程也是如此,多线程技术相当于是应用程序内部的"多任务"。
就好比一个应用程序内部有多个线程,其中一个线程等待IO操作时,可以切换执行其他的线程,完成其他的任务,所以对于多线程编写的程序,看起来程序能够更快的完成。
所以刚才说线程是操作系统对于程序运行过程的更加细致的划分与掌控,对于一个多线程程序,能够更加充分的利用CPU资源,看起来执行快了,是因为CPU的效率变高了,而不是程序的运行所需时间变少了
单核CPU和多核CPU使用多线程的区别:
对于一个单CPU的系统,对于多任务的实现就是并发,操作系统不断的进行着切换,将时间片分配给不同的程序,看起来像多个程序是共同运行的。其实不是的。
通过多线程的话,将一个应用程序本身拆分为多任务,如果像上面说的某个线程等待IO导致阻塞,可以执行其他的线程任务,那么将会提高CPU的利用率。
但是如果是类似1+2+3+4…+N的计算呢?
假设计算过程是均等的,这不会出现IO阻塞的情况,每一次的运算都是相同的,CPU本身也没有空闲等待的浪费,所以CPU利用率没有上升,相反还会有线程切换维护的开销,所以整体看性能或许略有下降
所以说,单核场景下,尽管多线程在有些场景下可以提高CPU的利用率,但是对于单CPU系统(单核)系统,在有些场景下,反而会降低整体性能。
因为有的时候你并不能提高利用率;而且有的时候即使提高了利用率,如果提高的那一部分利用率,还不足以抵消做的那些不该做的事情的开销,整体看并不一定是往好的方向发展。
于是,多核CPU就像多线程的救世主一样出现了。
对于多核CPU,能够真正的做到在同一瞬时,执行多个线程,是真正的并行。所以很显然,这种场景对于真正的并行,不管你的程序任务是什么样子的,对于多线程程序,必然能够提高程序的执行速度
多线程编程需要解决的核心就是互斥资源的访问以及如何高效的利用CPU
程序是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码。
程序的一次执行可以抽象为进程,是一个动态的过程,进程有它自身的产生、存在与消亡
进程是资源分配的基本单位,线程则是进程执行的最小单位,线程就是进程内部的一条执行路径。它共享进程的空间和代码
在很久很久之前的串行执行时,程序按顺序加载到计算机中并运行,程序独占计算机的所有资源,程序具有顺序性,封闭性和可重现性
多道程序出现之后,程序需要并发的执行,计算机的资源是共享的,而不再是某一程序运行后独享
所以不再是顺序的,而是间断的,也不再是封闭,也不再具有可重现性
以做饭为例,当只有你一个人使用厨房时,你可以随便
但是当多人共享时,如果你还把你自己切了一半的菜扔到那边,可能会被扔掉,可能会被用掉,当然也可能没事。
为了解决程序并发执行的问题,进程的概念被抽象出来,其实就相当于“一个厨房使用规章”被制定出来
所以说进程和线程就是操作系统用来管理维护一个程序的运行与切换而设计出来的一个概念,然后通过各种数据结构以及值等实现描绘出来
一个程序的运行主要包括下面的几个部分的数据:
程序段、数据段、PCB(Process Control Block)三部分构成了进程实体
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,独立分配资源和独立接受调度的基本单位
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特性:进程由程序、数据和进程控制块(PCB)三部分组成。
多人轮流使用厨房时,某一时刻的你到底是做完饭了?还是还在排队?还是正在做?你会有一个状态用来表述你现在的这种情况
最基本的状态包括:创建、就绪、执行、阻塞、终止
其中最核心的是:就绪、执行、阻塞
基本的状态切换流程如下:
状态切换流程:进程创建后,会创建PCB,以及相关的必须信息,然后就进入就绪状态,等待CPU的调度
一旦CPU对该进程调度执行,也就是该进程获得了时间片,那么就会进行执行
当时间片用完之后,如果任务还没有结束,那么就需要继续等待(比如你做饭需要5小时,然而每个人只允许2小时,如果2小时你做不完,你必须让别人先做,你重新排队来)就绪
如果一个正在执行的程序遇到了IO请求,这通常是比较耗时的,进程会进入阻塞状态
进入阻塞状态的进程一旦获得了想要的结果,比如IO完成,那么就再次进入就绪状态,等待CPU的临幸
若进程的所有操作完成后,就会进入终止状态
有些系统中,还会有挂起状态,可能系统需要让正在执行的程序暂停下来,也可能是资源不足了,将某些不重要的进程暂停。
挂起是更彻底的暂停,可以认为挂起是“暂时被淘汰出内存的进程”
阻塞状态获得资源后会进入就绪状态,而一旦挂起,除非解除这个状态,否则他将一直暂停,被抛出运行之外
阻塞是因为某些原因暂时不能被执行,挂起是直接将你暂停
当你做饭时等待水烧开,这就是阻塞,而如果是老大说我们几个人先来,于是他们几个轮流使用,然后你站门口看着,这就是挂起
挂起的含义:
比如资源不充足时,将一些不重要的进程暂时挂起,挂起是真正的暂停执行,是一种主动式的管理,阻塞则是被动的,挂起也意味着置换到外存中,而不是内存中,不管是活动阻塞还是活动就绪,他们都在内存中,具备了相关条件,IO完成或者获得CPU时间片,就可以进行执行;挂起(静止)状态,静止阻塞还是静止就绪,他们都是外存中,并不能够执行,他们还需要一个载入到内存的过程
一个静止阻塞的状态就相当于在外存中等待一个事件的完成,事件完成后,进入静止就绪状态,他此时还是不会得到CPU的调度,激活后才有机会得到CPU临幸
终止状态通常是从执行状态进行转换,一般情况下不管一个什么状态的线程,他只有被执行时,才会可能进入终止状态
但是,在某些系统中,父进程有权利终止一个子进程,所以说这种情况下,就可能从阻塞或者就绪直接转换为终止状态
进程是对于程序执行的抽象描述,那么进程控制块,这个对进程的描述,就相当于进程的元数据,用于描述进程本身
尽管实现很复杂,但是我们应该想象得到,操作系统内核都是C/C++,毕竟也只是一种编程语言,编程语言对于抽象概念的描述也只能是通过语言本身;所以说,他就是一个数据结构,记录了用于控制管理进程的各个数据项。
PCB 中记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
**进程控制块的作用:使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。**或者说,OS是根据PCB来对并发执行的进程进行控制和管理的
例如,当OS要调度某进程执行时,要从该进程的PCB中查出其现行状态及优先级;
在调度到某进程后,要根据其PCB中所保存的处理机状态信息,设置该进程恢复运行的现场,并根据其 PCB 中的程序和数据的内存始址,找到其程序和数据;
进程在执行过程中,当需要和与之合作的进程实现同步、通信或访问文件时,也都需要访问 PCB;
当进程由于某种原因而暂停执行时,又须将其断点的处理机环境保存在PCB中。
可见,在进程的整个生命期中,系统总是通过 PCB对进程进行控制的,亦即,系统是根据进程的PCB而不是任何别的什么而感知到该进程的存在的。
所以说, PCB是进程存在的唯一标识
进程控制块主要包括:
进程用来管理程序运行,对于一个运行中的程序总归要有个名字,这就是进程标识符;
计算机运行时各个硬件设备寄存器保存的值就是计算机的状态(如同做饭时厨房盆盆罐罐里面的东西);
进程有状态,这些状态信息主要用来进行调度,也就是安排任务需要的信息(可能你长得好看,就能多一次机会使用厨房);
另外还有一些对进程的控制,比如进程(线程)同步数据、程序地址等
在一个系统中,通常可拥有数十个、 数百个乃至数千个 PCB。
为了能对它们加以有效的管理,应该用适当的方式将这些PCB组织起来。目前常用的组织方式有以下两种
进程作为操作系统对程序一次运行的抽象描述
进程的基本信息相当于元数据,就好像表结构一样以及一些必备的数据结构
对于进程的掌控主要有三座大山:进程控制、进程同步、进程通信
如果一个员工是一个进程,进程控制相当于人事、财务部门,负责招聘薪资考勤等。
进程同步相当于项目经理,负责项目中各人员的任务分配调度。
进程通信就相当于一种工作方式、沟通形式,比如你给我一个SVN标签号并且告知我意图,我去库中检索指定标签修改的指定内容,就完成了一个任务的协作。
比喻或许不足够恰当,仅供个人理解。
进程作为操作系统对程序执行的抽象,那么就使用了足够多的数据项对进程进行描述,所有的信息都是为了进程的管理、维护、调度、切换等
操作系统抽象出进程概念的核心是为了运行程序,所以进程的执行态是最为核心的,其他的状态则是为了更好的控制管理进程以及进程的并发执行而附加的
所以,一定程度上来讲,操作系统对于进程的控制,可以认为是对于进程的不同状态之间完成切换所需要做的一系列事件处理
所有的核心操作有:
以上就是本次分享的所有内容,喜欢的朋友可以点赞、收藏、转发支持!
原文链接:https://www.cnblogs.com/noteless/p/10350112.html
https://www.cnblogs.com/noteless/p/10350186.html
https://www.cnblogs.com/noteless/p/10350215.html