Java 多线程系列文章第 1 篇
本文目录
1 进程为什么出现?2 进程的组成3 如何竞争资源(调度算法)3.1 FCFS3.2 RR3.3 SPN3.4 SRT3.5 HRRN3.6 FB4 进程状态4.1 三态图4.2 五态图4.3 七态图5 进程关系5.1 父子关系5.2 僵尸进程5.3 孤儿进程6 执行模式7 进程间通讯7.1 管道(Pipe)7.2 流管道(Flow Pipe)7.3 有名管道(Named Pipe)7.4 信号量(Semophore)7.5 信号(Signal)7.6 消息队列(Message Queue)7.7 共享内存(Shared Memory)7.8 套接字(Socket)8 总结
要讲线程,一般都得讲一讲进程,进程是何方神圣呢?下面来简单介绍一下。
先通过任务管理器看看 Windows 系统下的进程。
从图片来看,每一个进程都占有 CPU、内存、磁盘、网络等资源。站在操作系统的角度,进程是分配资源的基本单位,也是最小单位。
引入进程的目的:为了使多个程序能并发执行,以提高资源的利用率和系统的吞吐量。怎么理解这句话呢?一个程序在运行过程中会涉及很多操作,利用 CPU 计算、通过磁盘 IO 进行数据传输等等,我们知道当程序在进行磁盘 IO 的时候,因为速度问题,会比较慢,所在在这个过程中 CPU 会空闲下来,这会造成资源的浪费,正因为引入进程,在 A 进程进行磁盘 IO 的时候,会让出 CPU 给 B 进程,合理地利用了 CPU 资源,使得程序之间可以并发执行。
从 CPU 角度,执行过程是这样子的:CPU 一直在负责执行指令,进程之间互相竞争 CPU 资源,下图有 A 和 B 进程,在一个时间点,CPU 只执行一个进程的指令,因为 CPU 运行很快,所以在咱们看起来,像是多个进程在同时跑。这就是进程带来的好处:提高资源利用率,并发地执行多个程序。
当然引入进程也不是有益无害,它增加了系统的时间和空间开销。空间开销这个好理解,进程有自己的组成部分(下面会讲),这个就占用了空间。时间开销则是进程切换需要时间。
进程由 3 个部分组成,分别是程序代码、数据集、栈和进程控制块(Process Control Block)。
各自的作用如下:
进程之间需要竞争资源,一般都是竞争 CPU 资源,因为 CPU 运行速度太快了,其他介质都赶不上。有了竞争就需要有规则,就像游戏一样,每个游戏都需要规则,不同规则会有不同的侧重点,这个看过“最强大脑”这个节目的朋友就非常清楚,每道题都有不同的考核侧重点,有些是侧重空间思维、有些侧重逻辑推算等等。下面我们就简单地一一讲解竞争资源的游戏规则。
First In First Out(先来先服务):最先进入就绪队列的进程,先运行,运行到完成或者阻塞时,再重新调度。一般情况下,这种调度算法会和优先级策略结合,比如每个优先级一条队列,每条队列中的调度都使用 FCFS。
特点:简单、比较偏于长进程、相对于其他调度算法平均周转时间长。
Round Robin(轮转):进程按提交顺序存在就绪队列,依次轮流占用 CPU 资源,运行一段固定的时间,时间到后如果还没执行完,就继续进入就绪队列队尾,排队等待下次执行。
特点:公平、对进程的响应时间较短。
Shortest Job Next(最短进程优先):将预期占用运行时间最短的进程优先执行,直到运行完成或阻塞时,再重新调度。
特点:有利于短进程。
Shortest Remaining Time(最短剩余时间优先):新进程进来时,如果新进程的预计运行时间比当前进程的剩余运行时间更短,就抢占当前进程,
特点:有利于短进程,和 SPN 的差别在于抢占这个一点,因为抢占,所以效率会比 SPN 好一些。
Highest Response Ratio Next(最高响应比优先):当前运行的进程完成或者阻塞时发生调度,每次调度前,计算所有就绪进程的响应比,响应比高的进程优先运行。
响应比公式如下所示:
特点:有利于短进程,服务时间相同的进程,先来的服务会优先执行,长进程因为在等待的过程中,优先级越来越高,所以不会一直不执行。
Feedback (反馈):由多个就绪队列组成的反馈机制,它有如下规则:
进程执行过程如下图所示
特点:短进程有非常大的优势,排在前面的队列都是时间较短的。
以上就是几个抢占资源的调度算法的说明。
上面我们讲到,进程之间是在竞争资源,得到资源就运行,没得到就等待,这个需要有状态来维护,像很多系统一样,需要一个状态机。
三态图也是描述进程状态最简单最基础的图,它包含了进程的最基本的 3 个状态,分别是:就绪态、运行态和阻塞态。
Read(就绪态):进程已得到除 CPU 以外的其他所需资源。 Running(运行态):进程的指令正被执行。 Blocked(阻塞态):进程正等待资源或某事件发生。
进程三态图
就绪态的进程在被调度的时候,进入了运行态,如果时间片运行完或者有更高级别进程抢占资源,则变成就绪态等待再次被调度;如果发生事件(比如 IO 事件),则从运行态转到阻塞态,进入阻塞态的进程只能等待事件解除重新进入就绪态。
基于三态图,新增了 2 个状态,分别是:新建态和退出态。
New(新建态):进程正被创建。分配内存后将被设为就绪态。
Exit(退出态):进程已正常结束或出现异常结束。回收资源。
进程五态图
新进程刚创建还没有分配资源的时候是新建态,等到分配了资源,被加载后就进入就绪态。当进程运行完后,就从运行态进入退出态。
基于五态图,新增了 2 种挂起态,分别是就绪挂起态和阻塞挂起态。
就绪挂起态:另叫外存就绪态。由于内存容量有限,将原位于内存的就绪进程转存到外存(磁盘)上。
阻塞挂起态:另叫外存阻塞态。一样因为内存容量有限,将原位于内存的阻塞进程转存到外存(磁盘)上。
七态图
我们可以看出,图中新增了解除挂起的状态转换过程,一般是由于挂起进程优先级比较高或者内存空间足够,把位于外存(磁盘)的进程转存到内存中。
进程之间其实比较独立,比如我们在日常使用的 QQ 和微信,它们运行起来的进程有什么关系么?其实除了互相竞争资源之外,没有任何关系。
虽然上面说的进程之间没有关系,但是有一个特殊关系需要讲,就是父子关系。
先做个试验,验证进程的父子关系。操作步骤:
start cmd
启动另一个 CMD 命令行程序;start cmd
启动另一个 CMD 命令行程序;操作过程如下图所示。
通过 ProcessExplorer 可以很清晰看到这 3 个 CMD 进程之间的关系。(想要 ProcessExplorer 插件可以通过百度网盘下载链接:https://pan.baidu.com/s/19531gf5tD_of1CWxpFR9Dg 提取码:qhc6)
我们看到 Father、Son、Grandson 三个进程呈现出我们预料中的树形。那么什么是父子进程呢?简单的说就是在进程中创建出新的进程,这个新的进程就是子进程,一个进程可以有多个子进程,但是只能有一个父进程。在 Unix 系统中,父进程通过调用 fork()
创建子进程,父子进程有如下特点:
这里重点讲一下 Copy On Write,使用了这个技术,父进程创建子进程的时候不会复制所有数据到子进程,省了复制的时间以及减少了大量的内存。这个复制不是必要的,因为如果应用程序在进程复制之后立即加载新程序,那之前的复制工作就是浪费时间和内存了。
讲了进程父子关系,就免不了提一下僵尸进程和孤儿进程,下面分别介绍一下。
僵尸进程:子进程退出后,父进程没有调用 wait 或 waitpid 获取子进程的状态信息,子进程的进程描述符仍保存在系统中,这种进程叫僵尸进程。
僵尸进程的危害:僵尸进程会一直占用进程号,系统能使用的进程号又是有限的,如果有大量的僵尸进程,会因为没有可用进程号导致无法创建新的进程。
孤儿进程:父进程结束退出,而它的子进程还在运行,这时的子进程就叫做孤儿进程。孤儿进程就被 init 进程(进程号为 1)收养,init 进程将对孤儿进程完成状态收集工作。
孤儿进程没有危害,因为被 init 进程托管了,init 进程会处理孤儿进程的收集工作。
指令分为特权指令(只能由操作系统内核使用的指令)和非特权指令(只能由用户程序使用的指令),因为指令有特权和非特权之分,所以 CPU 也分为 2 种执行模式:系统态(可以执行所有指令,使用所有资源以及改变 CPU 状态)和用户态(只能执行非特权指令)。
CPU 的系统态和用户态之间的切换。
当进程之间需要数据传输、共享数据时,进程间就需要互相通讯,通讯方式有如下几种,这里只是简单概括一下,不展开讲,咱的重点在于多线程,进程咱们简单了解一下就可以,感兴趣的同学可以根据要点进行深入学习。
管道是半双工通讯,数据是单向流动,要建立进程间互相通讯,则需要 2 个管道,这种通讯方式只能在亲戚关系的进程间使用,比如父子进程。
流管道是管道进化来的,数据不再是单向流动,可以双向流动,但是依旧是只能在亲戚关系的进程间使用。
有名管道提供了新的功能,就是给管道设置名字,它改善了上面 2 种管道通讯方式,支持了非亲戚关系的进程通讯。
信号量相当于计数器,利用它来控制多个进程访问共享资源,当一个进程A在访问共享资源时,信号量防止其他进程来访问,只有当进程A不访问共享资源了,其他进程才能访问。
信号可以在任何时候发给某一进程,不需要知道该进程当前的状态,如果对方进程未执行,信号会存在内核中,直到进程执行后传递给它;如果对方进程是阻塞,则信号会延迟传递,等到对方进程阻塞取消后才传递给它。
消息队列是存放在内核中的链表,可以有多个进程对这个链表进行写入和读取,它解决了信号传递信息少、管道只能传输无格式字节流和缓冲区大小受限的缺点。目前有 POSIX 消息队列和 System V 消息队列。
共享内存即为一段能被其他进程访问的内存,多个进程访问同一个内存,达到了通讯的效果。
套接字就是我们网络编程里面的那个套接字,可以通过网络也可以在本机进行通信,它的好处在于可以跨主机进行通信。
总的来说,进程是程序在一个数据集上的一次执行过程,它就是程序运行起来的表现。这是我们学习多线程的开篇,希望通过这篇文章,让大家简单地了解进程是什么,后面我们再来深入了解多线程。