概述
在前几篇博客中,我系统的整理了上下文切换和用户态以及内核态相关的知识,在整理过程中多次出现进程以及线程的概念。在学习其它博客时,我发现大部分博客内容前后矛盾,无法做到自圆其说,作为读者我也看得云里雾里,完全搞不清楚线程和进程的真面目。因此通过本片博客,我打算抽象的描述一下进程和线程的关系及作用。
进程和线程
关于为什么要抽象的描述进程和线程是因为:进程和线程本身就是两个抽象的概念。
作为 java 开发者的我深受 面向对象 思想的影响(毒害),总是下意识的把任何概念都理解为一种对象。这对于理解一些抽象概念其实是起到反作用的,因为抽象概念本身就是看不见摸不着的,而对象可以理解为实际存在的东西,在过分追求属性和方法时很容易钻牛角尖。
下面我们直接进入正题:
用一句话来概括:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。
我们的电脑通过 CPU + 内存 + 光驱 + 键盘 + 显卡等等资源组成,然而实际运行时主要通过CPU、寄存器和内存来完成的。CPU执行的速度极快,除了寄存器勉强能跟上 CPU 的速度外,其他设备都相差甚远。这样在多个任务需要执行时,无论采用什么样的执行策略,在CPU看来都是轮流执行,哪个任务优先级高先执行哪个,哪个任务排队时间长先执行哪个。
关于CPU、寄存器,和内存我们可以这样抽象理解:CPU 就类似皇上,寄存器就类似皇上身边的小太监,内存就像站在下面的大臣。当大臣有任务需要执行时,先交给小太监,小太监再去禀告皇上。皇上执行完后将结果告诉小太监,小太监再去通知大臣。也就是说,CPU不直接和内存交互,而是通过寄存器作为中转站来交互,一个CPU会对应多个寄存器,CPU和寄存器交互的主要原因是因为寄存器快。至于为什么不把内存全部换成寄存器,除非你有钞能力。
当我们执行一段程序代码时,除了CPU之外,其他的资源如显卡等也必须就绪。等所有资源就绪后,CPU开始执行,这里除了CPU之外的所有资源就组成了程序的运行环境,也就是前面提到的上下文。当这个程序执行完或者时间片用完后就需要切换到其他程序,在被切换出去前需要保存当前任务的上下文,确保下次被CPU调度时可以接着上次的结果继续向下执行。
串联起来说也就是:CPU的轮流执行就是:CPU加载程序A的上下文,执行一段时间,CPU保存A的上下文,CPU加载程序B的上下文,执行一段时间,CPU保存B的上下文,CPU加载A的上下文,接着上次运行结果,CPU接着向下执行。。。。
进程和线程的概念就是根据该执行串抽象出来的:
进程就是包括上下文切换程序执行时间的总和:CPU加载上下文 + CPU执行 + CPU保存上下文
而进程的粒度太大,一个程序不可能只有一条逻辑,可能有很多条逻辑,每个逻辑实现一小部分功能,这些小功能组成整个程序。
也就是说,进程的执行过程也可以划分为以下过程:CPU加载程序A的上下文,cpu 执行任务a,cpu 执行任务b,cpu执行任务c,CPU保存程序A的上下文
任务a,b,c 共享整个进程的上下文,CPU 在执行过程中没有执行进程上下文切换。这里的a,b,c 就是线程,也就是更小粒度的CPU执行时间段。
总结以下:进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。
抽象描述
这里我通过一个工厂的例子描述进程和线程的关系:
-
CPU是计算机的核心,它就像一个工厂,时刻都在运行
-
工厂中电力有限,每次只能运行一个车间,也就是说单个CPU同时只能执行一个任务
-
进程就像车间,它代表CPU所能处理的单个任务。
-
除了车间,还必须有相应的工具车间才能运行。这里表示任务的执行不仅仅需要CPU,还需要其他资源。
-
单个车间有很多工人,这些工人一起维持车间的正常运转。这里的工人就类似线程
-
车间的空间是共享的,很多房间工人都可以进出。这里表示所有线程共享进程的公共资源
-
每个房间的大小有限,只能容纳一定数量的工人,当房间工人数量达到界限时,其他工人就不能进入了。这里表示对于一些共享内存,当存在线程占有后,其他线程必须等它结束才能执行。
-
有些房间只能容纳一个工人,这里可以使用锁,工人进去后把房间锁起来。这里表示互斥锁
-
有些房间可以容纳n个工人,可以在门口挂n个标识,工人进去时必须拥有标识。这里表示信号量
也就是说,操作系统的设计主要集中在以下三点:
-
多进程执行:同时可以并发执行多个任务,多核CPU计算机中可以实现并行
-
多线程执行:每个大任务可以被拆分为多个小人物并发执行
-
协调机制:一方面防止进程和进程之间访问,一方面允许线程和线程之间访问