打破砂锅系列:进程与线程
作为打破砂锅系列的第一篇,先“打破砂锅问到底”,解释一下打破砂锅问到底
这个俗语的由来:砂锅1即我们熬药煲汤用的烧制锅,极易破碎(至少以前的材质),而且一碎就会一裂到底。该句俗语原为“打破砂锅璺
到底”,璺读作wèn,指陶瓷、玻璃器物等上面的裂痕,与问同声,后来改为了现在的问字,用于比喻追究事情的根底(虽然我也不知道是不是这样)。开这个系列是为了深度掌握基本概念的根底,也希望能帮到读者。
前言
一个经典的面试问题,同样也是学习和工作中一直要用到的基本概念,要做网络和系统调优,做可编程网络,就得深挖到底。虽然自知追究到底会涉及很多原理知识,但为了学的明白点,多少还是深挖一下,遇到真不是自己搞的、搞不懂的东西再以基本概念或比喻跳过。
CPU是如何执行程序的?
为了能有个更清晰的认知,我们先从执行程序的基础——CPU开始:CPU是如何执行程序的?
这里用腾讯云的一篇专栏说明吧:CPU 执行程序的秘密,藏在了这 15 张图里,我做一些总结和补充:
图灵机
图片源自:http://www.kristergustafsson.me/turing-machine/
图灵机是现代计算机的祖宗,是图灵在1936年提出的,其基本思想是用机器模拟人们用纸笔进行数学运算的过程,他将该过程看做两种动作的组合:
- 在纸上写上或擦除某个符号
- 把注意力从纸的一处移动到另一处
图灵机的基本组成 :
- 纸带(tape):由一个个连续的等大小格子组成,每个格子可以写入字符。纸带好比计算机的内存,而格子中的字符就相当于内存中的数据;
- 读写头(head):可以左右移动,并读取、写入、擦除纸带上任意格子的字符;
- 状态寄存器(state register):存储图灵机的状态(如下一步读写头移动位置、写入符号等),图灵机状态有限;
- 控制规则 (table):即一个有限的指令表,根据图灵机当前所处的状态和读写头当前读的符号,告知图灵机下一步的动作:确定读写头下一步动作。并改变状态寄存器的值,
- 写入(替换)或擦除当前符号;
- 移动读写头(左右移动或不动);
- 保持当前状态或转移到另一状态;
其中还涉及、衍生出图灵完备、可计算性等一系列关键词,我们大致了解一下概念和组成就好,关键还是后面的计算机模型。
冯·诺伊曼结构
是一种将程序指令存储器和数据存储器合并在一起的电脑设计概念结构,也是现代计算机的基础。
图片来源:https://cloud.tencent.com/developer/article/1729507
这里我们跳过一些概念,直接到冯诺依曼结构下程序执行的基本流程:
图片来源:https://cloud.tencent.com/developer/article/1729507
-
首先CPU读取程序计数器的值,该值是指令的内存地址,然后CPU控制单元操作地址总线指定需要访问的内存地址,通知内存设备准备数据,准备好后内存通过数据总线将之灵数据传给CPU,CPU再将该指令存入指令寄存器;
-
CPU分析指令寄存器中的指令,确定指令的类型和参数,如果是计算类型的指令,就将指令交给逻辑运算单元运算;如果是存储类型的指令,就交给控制单元执行;
-
CPU执行完指令后,程序计数器值自增,指向下一条指令。(自增大小由CPU位宽决定,32位指令是4字节,64位指令是8字节)
上图中即简单的将数据1和数据2相加的指令和数据存储示意,指令有所简化,真正的指令应为01机器码。
0x200
:将0x100
地址处的数据放到R0寄存器中;
0x204
:将0x104
地址处的数据放到R1寄存器中;
0x208
:将R0与R1寄存器的值相加,并存放到R2寄存器中;
0x20c
:将R2寄存器的值放到0x108
地址上;
以上对计算机如何执行基本的计算指令就有了一个大概的认知,当然离搞懂还有太远的距离,就暂停在这吧。我们的所有程序都是由一条条的基本指令组成的,计算机执行程序无非是以上概念的复杂组合。
骑在前人的肩膀上
回到一开始的问题,什么是进程与线程?这里我们以维基百科的一张图说明:
图片源自https://en.wikipedia.org/wiki/Process_(computing)
这张图直观地描述了程序(program)、进程(Process)和线程(Thread)的关系,包含了我们需要了解的大部分概念。
首先左上角就是我们上一节所说的,程序由指令组成,存放在硬盘中,当需要执行程序时,硬盘中的程序被实例化到内存,CPU就可以执行这些指令从而让程序跑起来了。而进程与线程,就是这些程序指令的抽象。
std::thread::thread(Function&& f, Args&&… args);这种创建进程是怎么创建了线程?
现在知道线程和进程本质上是什么东西了,但为了搞懂为什么需要这种抽象,还要继续了解:
这时候放上比较正式的介绍:
线程:是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程:是指计算机中已执行的程序,一段程序从硬盘中取出实例化到内存中并由CPU开始执行,这段执行的程序就变为了一个进程(一个程序也可以产生多个进程)。进程曾经是分时系统的基本运作单位。在面向进程设计的系统(如早期的UNIX,Linux 2.4及更早的版本)中,进程是程序的基本执行实体;在面向线程设计的系统(如当代多数操作系统、Linux 2.6及更新的版本)中,进程本身不是基本执行单位,而是线程的容器。
他们的关系也很清晰了:进程是线程的容器。
实例化到内存的进程包含如下关键信息:程序计数器(记录当前执行哪条指令)、寄存器、变量存储等等保证这段程序能够执行的所有信息。一个进程可以被一个或多个处理器执行。
而一个线程是这个程序中一组能够被独立于其他指令执行的一段指令,
这张图可以看到,线程是共享相同的进程地址空间的,因此同一进程中的不同线程可以共享多数信息。
这里引入stack overflow中的一条回答:如果你正在读一本书,你希望休息一下或是换换口味,去看另一本书,但你希望等你回来继续读的时候能从你现在停止的准确位置继续。一种方法便是记住目前的页数、行数以及该行行第几个字,这三个数字便是你读书的“上下文”。CPU也一样,正在执行这段指令的时候,想过一会儿再来继续执行,为了等一下能马上回到这里继续执行,就必须有同样的上下文。为了可以继续在同样的位置执行,CPU必须知道更多上下文,那么这样一段上下文在内的指令便是一个线程。
正如你可以同时看很多本书,多个任务也可以共用一个CPU。更技术一点来说:一个线程是一组独立的CPU寄存器值,包含程序计数器(即指令指针)、栈指针等。
自己的总结
总结下来:
进程与线程的概念
进程:程序从硬盘实例化到内存让CPU可执行,这就是进程的概念。进程是系统进行资源分配和调度的独立单位,也是我们常说的系统并发执行的单位。
线程:线程是进程中的实体,是CPU执行的上下文,一组独立的CPU寄存器值,包含程序计数器、栈指针等,是操作系统调度和分派、CPU执行的最基本单位。也被称为轻权进程或轻量级进程。
进程与线程区别
最大的区别在于,进程是由程序实例化来的,而线程是进程内创建的上下文。
进程是资源分配的最小单位,而线程是操作系统调度的最小单位;
不同进程地址空间相互独立,而同一进程内的线程共享该进程的地址空间。而不同进程的线程互相是不可见的。
为什么有了进程还需要线程
因为进程是粗粒度的。操作系统没有了线程,就像我们读书没有了书签、页数,同一时间你只能读这一本,不然下次只能重头开始(记忆上次读到的位置也属于记录上下文)。同样的如果这一段内容读不懂,发生了“阻塞”,即便后面的内容和这段没关系,你也没办法先去看后面的部分。引入细粒度的线程,作为操作系统调度和执行的基本单位,就可以减少程序在并发执行时的时间、空间开销,提高并发性能。
最后,本文还是存在遗漏与问题,也许我后续会继续补充完善,如果感觉有问题欢迎随时交流。联系方式:wupeng426@bupt.edu.cn
版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
本文链接:
也有说沙锅的,但按辞典还是用砂锅吧 ↩︎