How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs

作者:Leslie Lamport 1979

原文:

make-multiprocessor-computer-correctly-executes-multiprocess-programs

译者:phylips@bmy 2018-03-24

译文:http://duanple.blog.163.com/blog/static/70971767201822515346732/

 

高速处理器可能乱序执行程序。如果处理器满足如下条件就可以保证执行的正确性:执行的结果与按照程序描述的顺序执行的结果一致。满足该条件的处理器我们就认为是sequential的。假设一台计算机由共享内存的多个这样的处理器组成。在设计和证明运行在该计算机上的多进程算法[1]-[3]的正确性时,通常基于如下假设:执行结果与这些处理器以某一串行顺序执行的结果相同,同时每个处理器内部操作的执行看起来又与程序描述的顺序一致。满足该条件的多处理器系统我们就认为是sequential consistent的。每个处理器内部满足sequential并不保证多处理器系统是sequential consistent的。在本文中,我们描述了一种sequential处理器(具有内存模块)之间的互联方法,可以保证最终的多处理器系统是sequential consistent的。

 

我们假设计算机是由多个处理器和多个内存模块组成,同时处理器间的相互通信通过内存模块完成(任何的通信寄存器都被认为是独立内存模块)。我们只考虑那些会向内存模块发送fetch和store请求的处理器操作。假设每个处理器都会产生一系列的内存访问请求(有时候可能要等待请求执行,但是这里我们不关注这一点)

 

以一个简单的两进程互斥协议(two-process mutual exclusion)为例。某个进程包含一个临界区(critical section),该协议的目的是要确保任意时刻只有一个进程会执行临界区代码。该协议如下:

How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs_第1张图片

 

很容易证明该协议可以保证临界区的互斥访问,当该程序在一个sequential consistent的多处理器系统上运行时,这两个处理器不会同时进入临界区。{!上面的程序在sequential consistent的多处理器系统上执行是满足互斥条件的,下面的内容主要是用来说明一个满足sequential consistent的多处理器系统需要什么保证,主要方法就是通过说明如果没有这些保证上面程序执行没法保证互斥}

 

首先我们观察到对于一个sequential处理器来说,process 1中的“a:=1”和“fetch b”可能以任意顺序执行(因为process1的程序执行只需要考虑它自己,对它来说这两个是不同的变量,可以乱序执行)。但是很容易看出,如果“fetch b”先执行的话会导致错误—两个进程可能会同时进入临界区。这就导出了对于多处理器计算机的第一个要求。

R1:每个处理器必须按照程序描述的顺序产生内存请求。

但是事实上由于value的store只有在这个value已经被计算出来的时候才能进行,因此要满足R1实际是很复杂的。处理器在发出一个value的内存fetch请求的时候,需要确保针对该value前一个store请求已经处理完成。为了最小化等待,处理器可以先向内存模块发送一个不包含具体value值的store请求{!这样可以先让内存模块准备好,让这个过程与计算过程并行,在计算出来value之后可以直接存储,加速store的执行}。当然了,需要保证这个请求在获取到要存储的具体value值之前不能被真的处理。

 

但是R1并不足以保证执行的正确性。考虑这样一种情况,我们假设每个内存模块有多个端口,同时一个端口为一个处理器(或者是一个IO通道)提供服务。现在a和b的value是存储在不同的内存模块中的,再考虑如下执行序列:

1)   处理器1向它跟内存模块1的端口发送了“a:=1”的请求。但是该内存模块此时正忙着处理来自某个其他某个处理器(或者IO通道)的操作。

2)   处理器1向它跟内存模块2的端口发送了“fetch b”的请求,此时内存模块2刚好闲着,于是开始处理。

3)   处理器2向内存模块2发送“b:=1”的请求。该请求需要等处理器1的“fetch b”请求处理完成之后再处理。

4)   处理器2向它与内存模块1的端口发送了“fetch a”的请求

 

现在内存模块1上有两个操作在等着被处理。如果处理器2的“fetch a”请求先被处理,那么两个进程就有可能同时进入临界区,那么协议就出错了。如果内存模块在确定服务端口的时候使用的是round robin调度算法,那么这种情况就有可能发生。

 

在这种情况下,只有在内存模块1上请求不是按照它们被接受的顺序处理的时候,才会发生错误。这就导出了下面的要求:

R2:来自所有处理器的内存请求,在每个内存模块上必须按照FIFO的方式进行处理。同时内存请求的产生过程要包含加入请求队列的这个过程。

 

R1意味着在当前请求加入到请求队列之前,处理器不能产生之后的其他任何请求。因此如果队列满了的话,它必须等待。如果是有两个或更多的处理器同时试图往队列中加请求,那么以什么样的顺序进行服务没有关系。

 

注:对于同一个内存位置来说,如果已经有一个写请求在队列里了,那么后续到来的读请求就不需要放入队列中了,直接把队列中最新的那个write请求对应的value值返回就可以了。

 

R1和R2可以保证:如果每个处理器都是sequential的话,那么由它们构成的多处理器计算机就是sequential consistent的。为了说明这一点,我们首先引入关于内存请求的一个关系描述:“->”。当前仅当满足如下条件时,我们就说A->B:

1)   A和B是由同一个处理器产生的,并且A是在B之前产生的,或者

2)   A和B是针对同一个内存模块的,并且A是在B之前加入队列的。(因此A肯定会在B之前执行)

 

容易看出R1和R2意味着,“->”是内存请求集合上的一个偏序关系。再加上每个处理器都是sequential的,可以证明如下结论:无论所有操作以什么样的sequential顺序执行,针对同一个value的store和fetch操作,如果A->B,就一定意味着A在B之前执行{!如果A和B是同一个处理器产生的,那么A在B之前产生,根据R1 从程序描述上A也一定是在B之前,由于它们操作的都是同一个value,那么肯定是在同一个内存模块,那么肯定是A先进入该内存模块的请求队列,那么也一定是A先执行;如果A和B是不同处理器产生的,根据A->B的定义,那么A也肯定是在B之前执行。另外A->B如果它们操作的是不同的value,是无法保证A一定在B之前执行的,因为它们可能是位于不同的内存模块,而分属不同内存模块的请求,在R1 R2成立的情况下,谁先执行也是没有定义的,同时谁先执行也不影响sequential consistent}。这反过来又证明了多处理器系统的sequential consistent。

 

{!考虑前面的两进程互斥算法,如前所述按照如下顺序执行会有问题(即R1不满足的情况),假设a b初始值为0:

fetch b

store b=1

fetch a

store a =1

对于单个处理器内部来看process 1上执行的是:fetch b; store a=1,这个执行顺序是sequential的(因为与store a=1, fetch b执行结果一致)。但是从全局来看,它不满足sequential consistent。对于上面的执行序列来说,fetch b 和fetch a得到的value都是0,而任意可能的顺序执行结果都不可能出现这种情况:

1)store a =1

2)fetch b

--------------------

3)store b=1

4)fetch a

可能的顺序执行(必须要满足1在2前,3在4前)有:

1234,1324,1342,3124,3142,3412,3124。对于这些执行顺序来说,排在第一个的要么是1要么是3,无论是1或者3,它们都会把a或者b中一个设置为1,因此不可能出现读到a和b都是0的情况。这也说明了前面的执行顺序是违反sequential consistent的。

 

另一方面我们看到,R1保证了所有内存访问是以程序中描述的顺序发出的,而R2则保证了对于同一个value的访问,一定按照进入队列的顺序执行。那么对于同一个value来说,它们的访问顺序就一定与程序中描述的顺序一致。对于所有处理器来说,它们看到的都是针对这个value按照程序描述顺序执行之后的结果。那么整个执行过程就是sequential consistent的。

}

 

R2要求内存模块必须以FIFO的顺序响应请求队列中的请求;这意味着如果队首是store请求且还未接收到待存入的数据,那么此时内存模块必须保持空闲。可以弱化条件2,以便在这种情形下,内存模块可以响应其它的请求,只需要保证所有发往同一个内存单元的内存请求按照它们在队列中出现的先后顺序执行,发往不同内存单元的请求可能会以乱序的方式执行。sequential consistent依然会保持,因为这种服务策略在逻辑上等价于认为每个内存单元均是一个有着请求队列的独立内存模块。(实际上由于这些模块可能需要共享一些硬件,会影响它们响应请求的速度和请求队列的容量,但并不影响顺序一致性的逻辑属性。)

     这些保证sequential consistent的要求排除了一些可以用于加速单个顺序处理器的技术。对于一些应用程序而言,以降低处理器速度作为代价来达到顺序一致性可能并不划算。在这种情况下,必须意识到不能依赖传统的用于设计多进程算法的方法去产生可以正确执行的程序。必须在机器指令的最底层去设计处理器同步协议,而验证这些协议的正确性将会是非常繁重的工作。

 

参考文献:

     [1] E. W. Dijkstra, "Hierarchical ordering of sequential processes," Acta Informatica, vol. 1, pp. 115-138, 1971.
     [2] L. Lamport, "Proving the correctness of multiprocess programs," IEEE Trans. Software Eng., vol. SE-3, pp. 125-143, Mar. 1977.
     [3] S. Owicki and D. Gries, 'Verifying properties of parallel programs: an axiomatic approach," Commun. Assoc. Comput. Mach., vol. 19, pp. 279-285, May 1976.

你可能感兴趣的:(分布式,架构设计)