进程与线程
进程
一个进程就是一个正在进行的任务,计算机可以同时做很多任务,启动多个进程,像个人电脑上同时开着浏览器浏览网面,开着音乐播放器听音乐,浏览器和播放器就是两个不同的进程。
一个CPU同一时间只能运行一个进程,多个进程同时运行只是看起来同运行,因为CPU会在多个进程间切换,每个进程运行几十或几百毫秒,看起来像同时在运行一样。现在的多核CPU可以同时运行多个进行,但在每一个核上同时也只能运行一个进行。
进程模型
在进程模型中,计算机上所有可运行的软件,通常也包括系统,被组织成若干顺序进程,简称进程(process)。一个进程就是一个正在执行程序的实例,包括程序计数器、寄存器和变量的当前值。从概念上说,每个进程拥有它自己的虚拟CPU。当前,实际上真正的CPU在各进程之间来回切换。
在一台多道程序计算机的内存中有4道程序。这4道程序被抽象为4个各自拥有自己控制流程(即第个程序自己的逻辑程序计数器)的进程,并且每个程序都独立的运行。当然,实际上只有一个物理程序计数器,所以在每个程序运行时,它的逻辑程序计数器被装入实际的程序计数器中。当该程序执行结束(或暂停执行)时,物理程序计算器被保存在内存中该进程的逻辑程序计算数器中。
进程创建
有以下4种主要事件会导致进程创建:
- 系统初始化
- 正在运行的程序执行一个创建进程的系统调用
- 用户请求创建一个新进程
- 一个批处理作业的初始化
启动操作系统时,通常会创建若干个进程,其中有些是前台进程,也就是同用户交互并且替他们完成工作的那些进程。其他的是后台进程,这些进程与特定的用户没有关系,相反却具有某些专门的功能。例如,邮件客户端,在后台运行定期查询是否有新邮件。这些后台进程称为守护进程(daemon)。在 Linux 中使用 ps 命令可以列出正在运行的进程,在 Windows 中可以使用任务管理器查看。
除了在启动阶段创建新的进程之外,新的进程也可以以后创建。一个正在运行的进程经常发出系统调用,以便创建一个或多个新的进程协助其工作。在所从事的工作可以容易地划分成若干相关的但没有相互作用的进程时,创建新的进程特别有效果。例如,如果有大量的数据要通过网络调取并进行顺序处理,那么创建一个进程取数据,并把数据放入共享缓冲区中,而让第二个进程取走数据项并进行处理,应该比较容易,在多处理机中,让第个进程在不同的CPU上运行会使整个作业运行行更快。
在交互式系统中,键入一个命令或者点击一个图标就可以启动一个程序。这两个动作中的任何一个都会开始一个新进程。
最后一个创建进程的情形公在大型机的批处理系统中应用。用户在这种系统中提交批处理作业。在操作系统认为有资源可以运行另一个作业时,他创建一个新的进程,并运行其输入队列中的下一个作业。
在 Unix 和 Windows 中进程创建之后,父进程和子进程有各自不同的地址空间。如果其中某个进程在其他地址空间中修改了一个字,这个修改对其他进程而言是不可见的。在 Unix 中,子进程初始地址空间是父进程的一个副本,但是这里涉及两个不同的地址空间,不可写的内存区域是共享的。某些 Unix 的实现使用程序正文在两者间共享,因为他不能修改,或者,子进程共享父进程的所有内存,但这种情况下内存通过写时复制(copy-on-write) 共享,这意味着一旦两者之一想要修改部分内存,则这块内存首先被明确的复制,以确保修改发生在私有区域。再次强调,可写的内存是不可以共享的。但是,对于一个新创建的进程而言,确实有可能共享其创建者的其他资源,诸如打开的文件等。在 Windows 中,从一开始父进程的地址空间和子进程的地址空间就是不同的。
进程终止
进程在创建之后,它开始运行,完成其工作。因为某原因进程终止退出,通常由下列条件引起的:
- 正常退出(自愿的)
- 出错退出(自愿的)
- 严重错误(非自愿)
- 被其他进程杀死(非自愿)
多线进程是由于完成了它们的工作而终止。
有些进程在运行中因有些运行条件不满足而退出
有些进程遇到的严重的错误,不由自己控制的错误,如执行了非法的指令、引用了不存在的内存或除数为零等。
最后一种,某个进程执行一个系统调用通知操作杀死某个进程。Unix 中这个系统调用是 kill, 也有一个同名的命令
进程的层次结构
某些系统中,当进程创建了另一个进程后,父进程和子进程就以某种形式继续保持关联,进程只有一个父进程,但可以有多个子进程。
进程的状态
尽管每个进程是一个独立的实体,有其自己的程序计数器和内部状太,但是进程之间经常需要相互作用。一个进程的输出结果可能作为另一个进程的输入。
shell 命令
cat chapter1 chapter2 chapter3|grep tree
中第一个进程运行 cat, 将三个文件连接并输出。第二个进程运行 grep, 它从输入中选择所有包含单词 tree 的那些行。可能发生这种情况: grep 准备就绪可以运行,但输入还不有完成。于是必须阻塞 grep, 直到 输入到来。
进程有三种状态:
- 运行态(该时刻进程实际占用 CPU)
- 就绪态(可运行,但因为其他进程正在运行而暂时停止)
- 阻塞态(除非某种外部事件发生,否则进程不能运行)
线程
在传统操作系统中,第个进程有一个地址空间和一个控制线程。事实上,这几乎就是进程的定义。不过,经常存在在同一个地址空间中并行运行多个控制线程的情形,这些线程就像(差不多)分离的进程(共享地址空间外)。
为什么需要线程
在许多应用中同时发生着多种活动其中某活动随着时间的推移会阻塞。通过将这些应用程序分解成可以并行运行的多个顺序线程,程序设计模型就变得更简单。
同一个进程下的多个线程共享同一个地址空间和所有可用的数据,这对于某些应用是必须的,而进程之间是不共享的
线程比进程更轻量及,所以他们比进程更容易(即更快)创建,也更容易撤销。在许多系统中,创建一个线程较创建一个进程要快10-100倍。在有大量线程需要动态和快速修改时,具有这一特性是很有用的。
多线程可以提高 CPU的利用率,在多CPU的系统中,真正的并行有了实现的可能
经典线程模型
进程模型基于两种独立的概念:资源分组处理与执行。有时将这两种概念分开会更好,这就引入了 线程的概念。
理解进程的一个角度是,用某种方法把相关的资源集中在一起。进程有存放正文和数据以及其他资源的地址空间。这些资源中包括打开的文件、子进程、即将发生的定时器、信号处理程序、账号信息等。把他们放到进程中可以更好的管理。
另一个概念是,进程拥有一个执行的线程,通常简写为线程(thread)。在线程中有一个程序计数器,用来记录接着要执行哪一条指令。线程拥有寄存器,用来保存线程当前的工作变量。线程还有一个堆栈,用来记录执行历史,其中每一帧保存了一个已调用 的但还没有从中返回的过程。尽管线程必须在某个进程中执行,但是线程和它的进程是不同的概念,并且可以分别处理。进程用于把资源集中到一起,而线程则是在CPU上被调试执行的实体
多线程与CPU利用率
要程序处理的快,要CPU都利用率提高,提高CPU利率的常用方法是多线程。但也要分做的任务是CPU密集还是IO密集
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。