控制结构(7): 程序计数器(PC)

// 上一篇:最近最少使用(LRU)
// 下一篇:线性化(linearization)


程序的每一行都是一个状态,对应的行指令。同步的情况下同一个pc一直自增,异步的时候,分裂出一个新的子pc,独立一颗子状态机。之所以要分裂一个pc是因为原来的pc后续的同步代码要用,而创建协程就会一开始就创建出一个新pc,这个pc专为协程里的状态机服务,则协程可以在异步点随时yield,也就是暂存独立的pc,跳转到旧pc去执行,然后再resume到新pc这里。基本上,就是两个状态机之间轮流占用时间片。一个独立的状态机一个pc,在同一个pc上的状态机片段,就可以在这个pc所代表的时间线上流式串起来,这就是所谓的react programming的原理,一个pc上面的流,就可以通过parser做filter,进而做成独立的pipeline.

多进程下,每个进程有独立id,每个进程的堆栈是独立的。

在多线程里,每个线程有独立的id,每个线程有独立的local storage,于是,调试器可以通过线程id和线程局部存储来标记不同线程上的调用链。因此,可以为不同的线程产生独立的函数调用堆栈,因为调试器可以把不同线程的函数调用链区分开来。

单线程里,一个函数的两次调用,他们的外围作用域是同一个,通过同一个外围作用域,一个函数的两次调用之间共享全局变量。同一个函数的两次调用所引发的后续调用链条,如果要区分开,需要各自一个独立的ID(类似上一段的pc),比如,在调用链条上到处传递这个ID可以做到。那么,有没有办法不显式传递这个ID,直接通过全局变量拿到呢?

一般来说是有困难的,假设使用一个全局变量标记函数一次调用的id,在该函数第二次调用的时候,id要变,但是如果你直接改变同一个全局变量,则上一次调用链里拿到的该id就被修改了。所以,必需函数的每次调用都有一个独立的id变量来标记。那么问题来了,在函数调用链条中,怎样拿到自己的那次调用的id变量?

由于一般语言并没有基础设施标记函数在动态之行中的独立调用标记,没有办法区分开应该拿第几个id变量。如果一个语言能支持函数执行过程中动态获取自己所在调用链的独立id,那就是一个很赞的语言。协程(corotine)提供了一种这方面的支持,一个独立的协程所引发的调用链,可以取到当前协程,挂载在当前协程下的变量就是协程所在的「local storage」。

这样调试器又有机会把所有的协程区分开来,可以为每个协程产生独立的调用链条。

我为什么要从进程->多线程->单线程这样绕一圈分析呢?因为,存在另一个更本质的问题:

怎样在多进程情况下,多进程之间的一个调用链条串起来。

显而易见的方案:

  1. 在所有的调用链条上传id,显然,这样的做法可以满足需求,但是所有的调用接口都需要知道这个id参数。做法2:

  2. 在进程出口和入口处传id,进程内部用独立的线程跑每一次的独立调用链条,通过唯一线程id来区分,线程里执行的代码只需从当前线程局部存储就可以拿到当前调用链id,这样不需要在线程内部不需要到处传id,只在进程之间出口和入口传id。但问题也明显:我们不可能无限创建线程。于是考虑3:

  3. 在进程出口和入口处传id,进程内部采用独立的协程跑每一次的独立调用链条,通过唯一协程id来区分,协程里执行的代码只需从当前协程局部存储就可以拿到当前调用链id,这样不需要在协程内部到处传id,只需在进程之间出口和入口传id。

很好,第3种方案是可行的,只需要你的语言支持协程。这样我们就可以在多进程之间的调用链条上有每次调用的独立id(也就是pc,程序计数器)。通过这一个id,可以给这样的调用链实现调试器。取代已经落后的基于单线程函数调用栈的调试器。

在不支持协程的语言里怎么办?考虑一种弱化的方式:函数闭包+支持函数内写任意代码(嵌套的class、嵌套的function,嵌套的import、include等加载其他模块代码等接口)。一个支持闭包的函数,函数闭包范围内的代码都可以访问函数的参数变量。这个函数的参数变量就是这个函数闭包范围内代码一次执行的「local storage」,此时方案改进为:

  1. 在进程出口和入口处传id,进程内通过函数闭包的方式执行用户代码(把这个外围的大闭包函数称为scope-function),用户代码可以通过函数参数变量获取当前调用链条的id。
  2. 由于这个函数闭包内的代码都可以访问到这个scope-function的参数,因此scope-funciton内部的代码不需要到处传id),那么一个问题是,scope-function内的代码如果import另一个模块的代码,不就超出当前scope-function的范围了吗?
  3. 很简单,通过scope-function的参数,替换掉scope-function内部执行的import,这个import的实现里,我们只要把scope-function的局部存储用来做需要import的代码的新scope-function的参数即可。
  4. 用户代码仍然不需要到处传id。

程序计数器,古老术语的内涵。本文的核心是说明“用独立id串起动态执行的有序调用链条”,不是进程、线程、协程等已有设施。

posted @ 2017-07-06 13:15  ffl  阅读(836)  评论(0编辑  收藏  举报