聊一聊进程和线程

本贴最后更新于 2338 天前,其中的信息可能已经天翻地覆

前言

前段时间一直在研究多线程相关的东西,乘着时间多又回顾了下操作系统关于进程和线程的知识。

必须要明确的概念

进程和线程是一种 资源,动态的存在于系统中。从实现上来看进程是一种数据结构,是静态的概念。通常一个进程对应于系统中活动的程序。

进程是用来干嘛的?

我们知道在早期的计算机操作系统中,cpu 一次只能执行一个任务。所以我们不能一边处理文档,一边听音乐。随着人们对多任务的需求,能够同时处理多个程序的操作系统应运而生。当然这里的同时是假的,在单核 cup 中,是通过 时分复用 来制造多任务处理的假象。(多核 cup 中才得以真正的并行,单核只是并发)要设计这样一个操作系统,首当其冲要解决的问题就是:用什么样的一个概念来刻画同时执行的程序? 于是便出现了进程。可以说进程就是为了刻画程序的 并发性 而存在的,同时又共享了系统的资源。前面提到了,进程和线程本质上是一种资源。

进程的状态转换

操作系统在引入进程后,不得不付出一定的代价,因为需要对其进行调度,管理。正是操作系统不断的调度进程才让不同的程序可以占用 cpu 资源,得到执行。这里有个理解上的误区,也是一直在强调的,即进程不是程序。很多人会把进程理解为程序。实际上是程序占用了进程。进程是动态的概念,程序是静态的概念。虽然平时提到程序时大都在指运行中的程序。

既然进程需要被调度,那么自然会有多种状态,操作系统的调度会让进程在这些状态中转换。简单介绍下进程运行的 三态模型七态模型

三态模型

三态模型.svg

三态模型假设进程被创建出来时是就绪状态等待被调度执行,当遇到等待事件时暂停执行,最终因为事件结束再一次处于就绪状态,等待操作系统调度。显然这个模型只表达了最基本的状态,不符合实际调度需求。

七态模型

七态模型.svg

对于进程的调度来说,七态模型更加符合要求。比如进程刚被创建出来还为分配资源的时候处于 新建态,当操作系统各项准备工作完毕,则进入 就绪态。值得注意的是:这里出现了挂起的状态,此状态表达的意思是,当内存资源不够用时,一些进程往往会被对换到磁盘上去,也就是外存。

如何描述进程

前面提到了进程是一种数据结构,那么需要用什么样的一种数据结构来描述进程呢?这里不去深究具体某种操作系统进程的实现,试着分析下进程需要包括哪些必要的信息。首先进程需要被调度执行,所以必须有一个相应的结构存储进程的标志信息,称之为 控制块(PCB,Process Control Block)。其次进程需要执行程序,所以有专门存放代码的 程序块,进程运行程序以及本身都要存放一些数据,所以存在一个私有 数据块。在 数据块 中可以开辟用户运行程序的 用户栈。于此同时,进程又必须拥有一个核心栈,用于在 内核态 执行内核程序。

有关 用户态内核态 的区别可以参考这篇文章内核态和用户态的区别

到这里,从概念上了解了进程的基本组成。对于简单理解进程是什么已经足够了。

如何调度进程

一般来说,操作系统会在不同的层次上对整个系统中运行的进程进行调度,最低一层次则是直接决定哪个进程的状态。通常所说的调度实际上包括:决定进程使用 cpu 的次序以及进程的切换工作。这里集中关注下如何判定进程执行次序,即调度算法。

常见的几种调度算法
  1. 先来先服务算法
  2. 最短优先算法
  3. 最短剩余时间算法
  4. 最高响应比算法
  5. 优先级调度算法
  6. 轮转调度算法
  7. 多级反馈队列调度算法

低级调度主要决定就绪进程或线程队列中哪一个可以立即占用处理器执行,执行的非常频繁。一般来说调度分为 抢占型独占型。内核关键程序使用 独占型,普通的则采用 抢占型,虽然后者更消耗系统资源,但是并发程度更高,使得性能更好。

这里简单介绍下 多级反馈队列调度算法。MLFQ 算法的核心思想是:建立多个优先级不同的等待队列,对列的优先级可以事先确定。优先级越高的队列的进程或线程优先被调度执行,但是优先级越高所分得的时间片就越少。优先级最低的队列会按照先来先服务算法调度。当一个耗时较多的进程或线程没有在所分得的时间片内执行完,那么它就会被加入到下一级队列。一直到最低一级。大家可能会注意到这样可能会产生 饥饿 的现象。比如一个特别耗时的进程,它最终会来到最低级队列,如果系统一段时间内有源源不断的进程被加入进来。那么这个进程就不会得到执行。怎么办呢??我们可以按等待时间让进程的优先级升高。这就形成了一个反馈机制。系统根据这个算法就能够比较合理的进行进程/线程的调度。

进程上下文切换和处理器状态转换

上下文切换

前面一直提到进程/线程的调度,实际上没有想象的把进程/线程队列入队,出队那么简单。一个进程放弃执行权利,把 cpu 让给另一个进程还要进行很多其他操作。统称为上下文(context)的切换。简单来说需要进行两个步骤:

  1. 将当前进程/线程的运行相关的信息保存下来,可以理解为保存现场,并移入等待队列。
  2. 调度一个就绪进程/线程占有 cpu 执行程序。

处理器状态转换

与进程上下文切换有关的是处理器状态转换,值得注意的是,处理器状态的转换不一定会引起进程上下文的切换。比如发生了系统中断,那么这个时候处理器会从用户态转化到内核态去处理中断处理程序,此时还是在进程的上下文中。然后可以恢复到用户态再继续执行进程。我的理解是:进程上下文切换是进程之间的事情,进程的上下文实际上是开辟在用户空间的。虽然执行中断或者内核例程也暂停了当前进程的执行,但是并不会破坏进程的上下文。因为中断或例程的上文是存在于内核空间的。

为什么要引入线程

如果说操作系统引入进程的目的是使得多个程序可以并发执行,提高系统资源利用率,那么线程就是为了让进程切换的代价更低,并发的粒度更细,进一步提高系统效率。实际上在现代操作系统中,例如 Linux,线程是操作系统直接调度的最小单位,而进程作为资源分配和保护的基本单位。一个进程至少包括一个主线程。

进程和线程的异同

与进程不同的是线程不拥有资源,它是一条程序的执行路径,拥有自己的私有地址空间和程序计数器。暂停时会保存自己的运行上下文。一个进程中的线程通常共享内部资源,所以线程间的通信要容易的多。多线程进程实际上和线程是包含的关系。进程像一个大管家一样,为线程服务:提供虚拟的地址空间,提供全局数据的管理等等。线程的职责更少,所以更加轻量。同处于一个进程中的线程切换的成本更低,大大提高了并发度。

线程的实现

多线程的实现分为三类:内核级线程(Kernel Level Thread ,KLT),用户级线程(ULT),混合式线程。个人水平有限,大家可以通过这篇文章 Java 实现线程的三种方式了解下 Java 多线程的实现原理。

小结

在简单了解了进程和线程的相关知识后,后面会着重探讨下并发程序设计中,同步,通信和死锁的问题。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...