深入理解进程和线程
进程和线程
进程
进程是计算机中已运行程序的实体,一个正在运行的程序就是一个进程。
进程模型
每个进程拥有自己的程序计数器、寄存器和变量等,操作系统会用一张表记录下每个进程中的信息。
就单核 CPU 而言:在某个时间点中,并不是所有进程都占用 CPU 资源,而是在 CPU 的高速切换中,每个进程都被 CPU 所运行。
如果某个时间点只有一个进程运行,其他的进程的变量等信息就会被操作系统使用一张表来记录,以便下一次 CPU 运算时调用,保持每次的结果相等,这张表称为进程表。
也就是说,每个进程在退出 CPU 调用的时候会把这进程表更新,以备下一次进入 CPU 时,数据的同步。
进程的状态
进程有三种状态:
-
运行态(这个时刻进程实际占用 CPU 资源)
-
就绪态(可以运行,但是因为其他的进程正在运行而暂时停止)
-
阻塞态(除非某种外部事件发生,否则进程不能运行)
这三种状态可以互相切换:
- 运行态 >> 阻塞态
操作系统发现进程无法运行下去的时候就会发生这个转换。比如:当一个进程从 I/O 设备读写文数据的时候,如果一直没有完成,那么就会进入阻塞状态。
- 运行态 >> 就绪态
这个转换是由于进程调度程序引起的,进程调度程序是操作系统的一部分,但是进程感觉不到进程调度程序的存在。因为系统认为一个进程占用 CPU 时间过长,那么就会让其进程退出,使其他的进程使用 CPU 的时间。也就是从运行态转换到了就绪态。
- 就绪态 >> 运行态
这个转换是上一个转换的逆向,原理相同,都是因为公平的原则,让所有的进程公平起见,占用太久的进程需要使用 CPU 时间,而让就绪态进程转换到运行态。
- 阻塞态 >> 就绪态
比如:当一个进程从 I/O 设备读写数据完毕之前是处于阻塞状态,而读写完毕之后就转变为了就绪态。如果这个时候 CPU 没有其他需要运行的进程,那么就会发生从就绪态转变为运行态。
线程
一个进程最少有一个线程,也可以有多个线程。线程有的时候与进程类似。假如进程是管家,那么线程就是苦力,要卖力的为进程干活儿。
线程像一种轻量级的进程,它比进程更容易创建,也更容易撤销。有时候,创建一个进程比创建一个线程多一到两个数量级。所以,这就体现出了线程的必要性。
假设你在一个文字处理软件中编写文字,当你觉得写的文字足够多以至于需要保存了,如果这个文字处理软件是一个单线程进程,那么在你保存的同时,你就不能做其他的事情,比如继续编写文字。或者发生了另外一种情况:这个文字处理软件开发者觉得这个软件不够好,对其做了改进,现在这个软件可以自动保存了。
这样就不需要担心编辑很多内容突然断电导致灾难性的后果。
第二天你还是生气的对软件开发者说道,在我输入十几个字之后电脑就动不了了!
在单线程的进程中,程序只能做一件事,也就是说,按照上面的例子,文字编辑器添加了自动保存的功能,在保存的同时,用户不能够对其输入,只能够等待保存完成之后继续输入。
再举一个例子:
老刘正在坐着清闲的看着报纸,突然接到电话,说他的孙女发烧了需要照顾,正在他在赶路去孙女家的时候,朋友老李打电话让他出去喝茶但是他不得不照顾完孙女再去喝茶。
老刘就类似于一个线程,而他接到电话要去照顾孙女的时候,他就不能答应老李的邀请。
要是老刘能分身的话,该多好!
事实上,老刘不能分身,而计算机中,可以使用多线程来完成任务。
多线程
上面所说,一个进程至少有一个线程,也可以有多个线程。为什么我们不使用多个进程合作而使用多个线程合作呢?
试想:前面的文字处理软件,我们可以再开启一个进程来为软件保存内容,可是,这样能做到吗?
一个进程可以认为自己独占了内存,是看不见其他进程的内存。那么上面的想法,还可行吗?
使用线程就能完美的解决这个问题了,因为多个线程是可以共享一个进程里的资源。
这样那个文字处理软件开发者可以使用多个线程共同合作来完成任务:一个线程用来更新显示,一个线程用来保存,一个线程用来监听键盘鼠标......
我们可以称用来保存的那个线程为守护线程。
经典的线程模型
线程的实现有 3 种模型:
- 多对一(M:1)的用户级线程模型
- 一对一(1:1)的内核级线程模型
- 多对多(M:N)的两级线程模型
用户级线程模型:
在这个模型中,线程的创建、调度、同步等所有的细节全部由进程的用户空间线程包来处理,这样就不需要内核来接管这些操作,这种模型可以在不支持线程的操作系统上实现。
在用户空间管理线程的时候,每个进程就需要一张线程表,用来记录与跟踪进程中的线程,这张表与进程表类似, 但是线程表仅仅记录各个线程的属性。比如:每个线程的程序计数器、堆栈指针、寄存器、状态等等。
用户级线程有一个优点,就是允许每个进程有自己定制的调度算法。例如,在某个应用程序中有些垃圾收集线程的应用就不需要担心在不适合的时候停止导致食物。
用户级线程有一个问题:如果一个线程开始执行,那么该进程中的其他线程就不能执行,当这个运行的线程被阻塞了,那么操作系统将认为整个进程都是阻塞住了,进而该进程中的其他线程无法调度。
线程与进程类似,但是也有不同之处:
a. 线程没有时钟中断
b. 线程可以调用 yield 函数,在执行这个函数之后就可以调用线程调度程序来选择另外一个需要运行的线程。而进程不可以。不同的进程所拥有的资源都是相对独立的,所以不同进程之间是竞争关系,而线程之间是相互合作用来完成某个任务而存在,是合作关系。所以进程没有那么高尚,将自己宝贵的时间片让给其他进程。
内核级线程:
在这个模型中,进程中就不需要线程表了,也没有线程表。但是,内核中有用来记录系统中所有线程的线程表。内核中的线程表与上述用户级线程表记录的信息是一样的。
当一个进程的线程阻塞住了,内核可以选择是让其阻塞住线程的进程中其他线程执行或者是让其他进程中的线程执行。
在内核中创建与销毁线程的开销相对来说是比较大的。
混合实现:
用户级线程与内核级线程都有各自的优点与缺点,人们研究了许多键这两种优点结合起来的方法。
有一种方法就是,使用内核级线程,然后将用户级线程与讴歌或者全部内核级线程多路复用起来,这样,内核只识别内核级线程,并对其进行调度,其中一些内核级线程会被多个用户级线程多路复用。这样每个内河集线程有一个轮流复用的用户级线程集合。