浅谈对多任务(并发/并行)的理解

为什么需要多任务

  从历史的角度来看,多任务执行诞生之初,最主要的目的是为了处理IO密集型的任务。由于IO的速度远远慢于CPU的执行,所以如果没有多任务处理系统的话,CPU将在等待IO的时候无事可做,这是一种极大的浪费(尤其是在CPU时间非常宝贵的计算机诞生早期)所以我们需要给CPU配上“任务调度”系统,以控制CPU在进行IO等待的时候能切换出来,去做别的事情。
  另一方面,由于摩尔定理在21世纪初撞上了天花板,并行执行成为了提高CPU运行速度最好办法之一。单任务处理无法利用现代处理器的多执行流,所以多任务处理从这个角度来讲是一种必须

进程和线程

  在现代操作系统上,多任务都基本上都是通过进程和线程实现的(还有一个多任务概念是协程,不过今天暂时先不讨论)。与协程不同,进程和线程都可以被看作独立的执行流,即都是异步的,如果不显示的进行进程/线程间交流,则概念上无法判断进程和线程的执行先后顺序、以及执行时间片。

进程

  按照诞生顺序,先从进程说起。从抽象的概念上讲,一个进程必须有自己的执行上下文执行空间系统性信息

执行上下文

  先从抽象的概念说起,所谓执行上下文,就是能够清晰的表达:我执行到哪里了的数据结构。执行上下文要求CPU能够保存某一个执行上下文数据结构到内存中,并在将来的某个时刻从这些数据中恢复,要求恢复以后和保存之前执行起来没有区别。

所以从抽象的概念讲,一个执行上下文需要实现以下功能:
  1. 保存执行上下文
  1. 从执行上下文中恢复
  1. 上下文在恢复后和保存前,在代码的执行上没有区别

  干讲有点枯燥,正好,这种对执行上下文的要求跟抽象体系中的接口有异曲同工之处,所以可以把执行上下文看作如下的Java接口。

interface ExecutionContext {
    
    
	// 保存当前的执行上下文到某个内存地址
	boolean saveContext(Address add);
	// 从某个执行上下文中恢复
	Context resumeContext(Address add);
}

  在现代CPU上,执行上下文一般由以下几个部分组成

  1. PC(程序计数器指针) 指向下一条将要执行的指令
  2. 寄存器的值
  3. 执行栈
      只要能够妥善存储以上的三个数据结构,保证在下一次恢复前没有被修改,就能保证程序执行的情况和被存储前没有区别。

执行空间

  然而,光有执行上下文不足以描述一个进程。主要是因为并非程序中所有的数据都存储在栈上,程序需要在执行的过程中使用大量的数据(包括程序代码本身),这些数据存在的地方,就是程序空间。从最简单的抽象来讲,它可能类似于

interface Space {
    
    
	// 从空间中读入
	byte[] read(int num);
	// 向空间写
	byte[] write(int num);
}

不过实际上,现代CPU/操作系统对执行空间都支持 读/写/执行 权限管理,分页调度,虚拟映射,动态链接等等复杂的机制。

系统信息

  说实话这个词是我自己创造的,很可能早就有一个专业的词汇来描述这个概念了(原谅本人才疏学浅,很多专业书籍没时间细细品研读)。但是我想表述的概念是非常清晰的,所谓系统信息,就是操作系统(或者进程调度系统)用以识别、标志一个进程,或者附加给进程,用于其他用途的额外信息。比如:进程ID,进程权限,进程打开的文件描述符,进程实际用户ID,进程有效用户ID等等等等。这些东西可以放在一个struct里,然后把指向该strcuct的指针放在某个和进程强绑定的数据结构里即可。

权限

  权限是最值得一提的系统信息,因为多任务系统实现的前提是每个任务之间的隔离,所以非常有必要通过引入权限系统来避免不同进程之间互相操作。当然,有了隔离,就需要设置通信方式,不过这超出本文想要探讨的范围了。

继承关系/调用关系

  此外,进程需要维护自己的继承关系,因为继承关系和权限有关。

线程

  现在讨论线程,我们按照和讨论进程相似的方式来进行。

执行上下文

  毫无疑问,进程是一个执行流,一个执行流显然需要自己的执行上下文。

执行空间

  就执行空间的概念来说,线程和进程是一样的,都是用于存储运行时数据的地方。但同时,执行空间也是进程和线程最大区别的地方:
每个进程都有自己的执行空间,一般不允许访问别的进程的执行空间 读/写/执行 数据
但是一个进程的所有线程的执行空间都是该 进程的执行空间,实际上,一般来说,一个进程的线程的栈(栈应该被认为是执行上下文的一部分,还记得吗)是属于该进程的执行空间的。也就是说,一个进程的线程其实是可以访问别的线程的栈的,只要它知道另一个线程的栈的地址 :)

系统信息

  与执行空间类似,线程的系统信息也是基于创建该线程的进程的,只不过一个进程需要对它所创建的每一个线程分配一个唯一的线程id来标志它创建的 不同线程,也有可能需要给线程附加其他信息用作他用。

权限

  一般来讲,一个进程创建的 所有线程的操作权限和该进程是一样的。所以线程不需要维护自己的权限信息,要查我的权限信息?给你一个指向创建我的进程的指针,你找他去问好了:)

继承关系/调用关系

  线程可以被主线程创建,也可以被非主线程创建,但是由于一般来说一个进程所有的线程的权限是一样的,所以没有很大必要区分他们的继承关系。

总结

  从执行流的角度来理解,线程和进程都有执行上下文执行空间,从操作系统的角度理解,他们还需要系统信息
  执行上下文保证程序被加载进内存以后寄存器中没有需要保存的内容(实际上等于空寄存器),并且栈为空,并且PC程序计数器指向程序入口函数,执行空间的初始化信息一般来说被放在可执行文件中,系统信息由操作系维护。实现了以上三个数据结构,并实现了执行的概念,基本上就可以被叫做进程和线程了。
  而进程和线程最大的区别在于线程基本上只需要维护自己的执行上下文,使用进程的执行空间和系统信息就可以了。
  好了,本来今天还想讲一下自己对同步/异步和锁的理解的,暂时就这样吧,生活很艰难,事情还多呢,溜了。

猜你喜欢

转载自blog.csdn.net/weixin_40085040/article/details/107592152