文章目录
- 前言
- 1. 进程是什么
- 2. 进程的相关属性
- 3. 线程是什么
- 4. 为什么引入线程
- 5. 进程和线程的区别
前言
上一篇博客,我们讲到了CPU和操作系统,今天我们讲一个操作系统中一个非常重要的概念—线程和进程
1. 进程是什么
每个应用程序运行于现代操作系统之上时,操作系统会提供⼀种抽象,好像系统上只有这个程序在运行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了⼀个进程的概念来完成的,进程可以说是计算机科学中最重要和最成功的概念之一。
进程是操作系统对⼀个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
2. 进程的相关属性
首先我们先站在操作系统的视角下,如何管理进程?
- 先描述一个进程是怎么样的
创建一个进程控制块(PCB),是一个非常大的结构体,有很多很多的属性 - 再把多个线程组织起来
比如,Linux 这样的操作系统,使用链表这样的形式,把多个PCB串到一起
PCB中的一些属性:
pid,内存指令,文件描述符表,进程状态,进程的优先级,进程的上下文,进程的记账信息
-
pid (进程id)
进程的身份标识符 -
内存指令
是一组指令
进程需要知道要执行的指令在哪里,指令依赖的数据又在哪里,
进程运行的过程中,需要依赖内存资源
-
文件描述符
进程,在运行过程中,很多时候,需要和硬件设备,去进行交互,而硬盘上的数据是以文件的形式组织的,在进程读写文件的时候,需要先"打开文件"。
每次打开一个文件,就会把这个文件信息,保存到文件描述符表中,表里的每一项,就对应这一个打开了的文件。
操作系统中,会把很多资源,都抽象成文件来表示,不一定是硬盘中的资源。
进程是操作系统中,资源分配的基本单位
将下面这几个之前,我们再讲几个名词。
第一个:分时复用
早期计算机,是"单任务"的操作系统,同一时刻,只能运行一个进程,要想运行下一个进程,就得结束上一个进程。
而多任务操作系统 (在有多核处理器之前,多任务操作系统都已经出现了)。即使 CPU 只有一个核心,也能同时运行多个进程,这是为什么呢?就是因为分时复用。
把一个单位时间,分成很多份。
第一份,运行进程1的指令
第二份,运行进程2的指令
…
CPU 运行速度足够快,上述的切换过程,也会非常快,快到超出人类的反应时间。使人看起来感觉好像这些进程在 "同时执行"一样。
第二个:并发执行
把一个 CPU核心上,按照 分时复用,执行多个进程这样的方式,称为并发执行。人看起来是同时执行的,但从微观上来看,其实一个CPU在 串行执行,切换速度极快。
第三个:并行执行
把多个 CPU 核心上,同时执行多个进程这样的方式,称为 并行执行,现代 CPU 在运行这些进程的时候,并发和并行是同时存在的。
程序员在写代码的时候,无法区分这些进程是并发执行还是并行执行的,所以也会把并发和并行,统称为 “并发”
-
进程状态
进程中有很多种状态,其中有两个最为典型。
(1) 就绪状态 --> 随叫随到 ,进程可以随时到 CPU 上进行执行
(2) 阻塞状态 --> 进程当前不适合到 CPU 上执行 -
进程优先级
这么多进程,他能都可以去 CPU 上运行的机会是均等的吗?
有些进程,就是要优先级更高一些,分配到更多的 CPU资源。
例如在打游戏的时候,QQ也在运行,毋庸置疑,游戏要分配到更多的 CPU 资源,不然游戏就会跟卡。 -
进程的上下文
进程调度,一个进程执行一会,就会失去 CPU,过了一段时间之后,进程还会回到 CPU 上执行。
是沿着上次执行到的状态,继续往下执行,而不是重头执行。
进程在 CPU 运行的过程中, CPU上的各种寄存器,就表示了进程运行的 “中间状态”。
保存上下文:把CPU中的这些寄存器中的值,保存到内存中(PCB 的对应属性中)
恢复上下文:把 PCB 中刚才保存的属性,填写回到对应的寄存器中 -
进程的记账信息
统计功能,统计每个进程都在 CPU 上运行了多久,如果发现某个进程,好久没有分配 CPU 资源了,就会给这个进程倾斜一些资源。
3. 线程是什么
一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码.
4. 为什么引入线程
在没有引入线程之前,可以通过多进程编程的方式,实现并发编程的效果。但是进程整体是一个比较 “重” 的概念,创建 / 销毁进程的开销很大,尤其是频繁的创建和销毁。
为了解决上述问题,引入了线程 (Thread),轻量级进程。
每个进程,都相当于是一个要执行的任务,每个线程,也只是一个要执行的任务 (运行的一段代码指令)
5. 进程和线程的区别
-
进程中包含线程
每个进程中,都包含一个或多个线程。
在Windows的资源管理器中,看不到进程内部的线程,都是借助一些调试工具 (VS 的调试器,Windbg等) -
进程是操作系统资源分配的基本单位
CPU,内存,硬盘资源 (文件描述符,网络带宽…
进程内部管辖着多个线程,会共享上述的内存资源和硬盘资源。
进程创建,需要申请资源;进程销毁,需要释放资源,都是重量级的事情。对于线程来说,只是第一个线程创建的时候 (和进程一起创建的时候) 申请资源,后续再创建线程,不涉及到自愿申请操作 (干得少,速度快)
只有所有的线程都销毁 (进程也跟着销毁),才会真正释放资源,运行过程中销毁某个线程,也不会释放资源。 -
线程是 CPU 上调度执行的基本单位
如果一个进程包含多个线程,此时,多个线程之间,各自去 CPU 上调度执行。
比如说,一个进程中有线程1,2,3
可能是,线程1去 CPU核心1 上执行
线程2去 CPU核心2 上执行
线程3去 CPU核心3 上执行 ,并行操作
也有可能是,线程1,线程2,线程3在一个核心上来回切换 (并发)
也可能是 线程1,线程2 在一个核心上来回切换,线程3在另一个核心上,来回切换。
上述内容,咱们程序员感知不到,也干预不了,全是由操作系统内部的 "调度器"自行完成的 -
线程中的一些属性
有 调度相关,状态,优先级,记账信息,上下文。
而文件描述符表和内存指针共用这线程的。 -
引入线程后的一些问题
咱们举一个场景,一些人去吃鸡腿,其中人代表线程,鸡腿代表任务量,人吃鸡腿的动力代表CPU资源,不同的房间代表不同的进程。
场景一:
两个房间,两个桌子,两个滑稽老铁,吃 100 只鸡腿,每个房间的都放入 50只鸡腿。
引入多线程编程,效率提高了,但是搞个房间,搞个桌子,搞个滑稽,整个的开销也不小
场景二:
一个房间,100只鸡,两个滑稽来吃。
引入多线程方案。
此时,也是每个滑稽吃 50 个,同时吃
这样整个效率大幅度提高,并且也节省了房子和桌子的开销。
场景三:
如果引入多个线程呢?
一个房间,100只鸡,四个滑稽来吃
整体的效率还会进一步提升。
但是如果有几十个甚至100个滑稽一块去吃呢?
虽然提高线程的数目,能够提升效率,但也不是 “线性增长的”,线程数目达到一定程度之后,就算有再多的线程,也没法起到效果了,因为桌子上的空间有那么大。而且挤来挤去,会拖慢效率。
线程数目如果太多,线程的调度开销也会非常明显,因为调度开销会拖慢程序的性能。
场景四:
多个滑稽老铁,共同抢夺一只鸡呢?
如果两个滑稽老铁,看中了同一只鸡,此时就可能发生冲突。
就会引发线程冲突问题,造成线程不安全,这样的冲突,可能会使得代价出现bug
场景五:
有个滑稽老铁,脾气火爆,没有抢到鸡大腿,直接掀桌了,(╯‵□′)╯︵┻━┻。
一个线程抛出异常,可能就会带走整个县城,所有的线程都无法继续工作。
可能即使捕获到异常,处理掉,也不一定会造成进程终止
下面便是图形化的解释,大家可以看看。
下一篇博客,我们就要使用 多线程来编写代码了,我们不见不散