进程和线程是操作系统中非常重要的两个概念,无论在开发或者是面试过程中都会经常遇到有关问题。最近根据自己所查阅的资料,对进程和线程做部分总结。
一、进程
1、什么是进程
从概念上来说,进程可以理解为程序的一次执行,包括在本次执行过程中的指令和所用到的数据。进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。
有关进程的理解,有一个有趣的例子:厨师做蛋糕。将厨师比作CPU,做蛋糕的菜谱比作程序,做蛋糕的原料比作数据,那么一个厨师通过阅读菜谱,用原料来制作蛋糕的过程就可以看成是一个进程。
2、内存中的进程
那么一个进程在内存中是如何存在的,但进程切换的时候又会发生怎样的变化呢?
在Java中,一个进程需要在进程中存储四部分内容:用户栈,堆,程序代码存储,程序数据存储,如图1。用户栈中保存静态创建的变量,堆中保存动态创建变量。程序和数据从可执行文件中加载,存放在各自对应位置。
CPU在执行一个进程时,有很多情况会需要进行进程的切换,例如中断,时间片用尽等。此时,操作系统需要将上一个进程的信息都保存起来,然后再执行下一个进程。需要保存的信息包括:寄存器中的信息,进程执行到的位置,打开的文件等等。这些信息全都用一个叫PCB(进程控制块)的数据结构存储。
3、进程的状态以及切换
一般来说进程有五个状态:创建,就绪,执行,阻塞和退出。其中经常讨论的是进程在就绪,执行和阻塞三种状态之间的切换。
当一个进程被创建完成后,它就进入就绪状态,等待操作系统的调度。一旦操作系统按照进程调度算法,调度改进程是,他就进入运行状态,
开始执行。在执行状态下,如果该进程的时间片用完,则它将保存进程切换 的相关信息,退出运行环境,进入就绪状态;如果进程因为某些事件(比如IO操作)需要等待时,进程就进入阻塞状态,进程同样需要保存进程切换 的相关信息,退出运行环境。而当时间操作完成后,阻塞状态的进程又会进入就绪状态。
4、进程调度算法
进程调度算法就是用来决定哪一个进程占据CPU的算法。总体而言,进程调度方式可以分为抢占式和非抢占式。
非抢占式算法指调度程序一旦把 CPU分配给某一进程后便让它一直运行下去, 直到进程完成或发生某事件而不能运行时,才将CPU分给其它进程。这种算法比较简单,且系统开销小,比较适合批处理系统。非抢占式调度算法包括FIFO(先来先服务算法),短作业优先算法。
抢占式算法指当一个进程正在执行时,系统可以基于某种策略剥夺CPU给其它进程。非抢占式算法包括时间片轮转算法,优先级算法,多级队列反馈算法等。
二、线程
虽然有进程调度算法能够管理进程之间的切换。但是每个进程拥有自己代码,数据以及文件。但是频繁的进程切换会带来很大的开销,因此就有了线程的概念。进程是线程的容器,每一个进程至少有一个线程,也就是主线程。同一个进程内的线程,共享代码数据以及文件,因此大量减少了切换的开销。值得一提的是,线程之间共享的是代码,数据,文件等数据,至于寄存器中的值,堆栈中的内容,则不是共享的。
1、线程的优点
为了讲明白线程相比于进程的优点,这里以文字处理器(例如Word)作为例子。Word软件在工作时,需要处理很多方面的事务。例如,接受用户输入,定时保存,展示输入内容,显示图形等等。如果不用线程,而是用进程处理,那么有两种方式:
一种是用同一个进程处理全部事务,既要负责接受输入,又要负责保存等等工作,那么这就带来一个后果,Word在同一时刻,只能干一件事,输入时不能保存,不能显示。显然这是不能接受的。另一种方法是用多个进程来处理,但是每一个进程都需要独立的内存地址和数据代码,这个开销显然是巨大的。
综上我们可以明白,
线程的优点就是能够实现同时处理多种事务,并且不花费很大的开销。
2、线程的实现
讲线程实现之前,需要先说一下操作系统的用户空间和内核空间。用户空间是指应用程序运行的地方,而内核空间指操作系统或驱动程序运行的地方。根据实现位置的不同,线程的实现可以有这三种方法:
完全在用户空间层实现。例如下图1,将所有的线程都交给用户来实现,操作系统只负责管理进程。这样做的好处是,线程的切换完全不需要内核的介入,使得其切换变得很快,开销很小。但存在一个巨大的问题是:对于操作系统而言,它只看见内核空间中的进程,而看不见每一个线程的状态。如果一个进程中的某一个线程阻塞了,操作系统就会认为整个进程都阻塞了,从而让该进程内部所有的线程都处于阻塞状态。每一个用户线程对应一个内核线程。如下图2.这样一来,线程的创建和执行等过程,就有操作系统来介入。这种方式能够避免一个线程由于别的线程的阻塞而阻塞。但其缺点是开销比较大。混合方式,让用户空间中的多个用户线程对应到一个内核线程中。这样一来,既能够一定程度上避免线程由于别的线程阻塞而阻塞,同时也减少系统开销。
3、线程池
如果我们使用线程的方式是,当我们需要一个线程时,就去新建一个线程。那么一旦遇到数量很多而且执行很短一段时间就停止的情况,这样大量创造线程就会增大系统开销,并且线程的创建和销毁也需要时间,会降低系统的执行效率。Java中通过线程池的方式来解决针对这一问题。
线程池是一些已有线程的集合。当一个任务产生需要被执行时,操作系统会将其加入一个阻塞队列。此时如何线程池中有空闲的线程,那么空闲线程便去执行这个任务。当一个线程执行完一个任务时,该线程不是被销毁,而是进入空闲状态,准备接受下一个任务。线程池的使用避免了系统不断创建和销毁线程的开销,提高了系统的效率。
转载请注明原文地址: https://ju.6miu.com/read-11752.html