[译] 线性一致性

原文:Linearizability: A Correctness Conditon for Concurrent Objects

只翻译了一部分,译文如下。


并发进程共享的数据对象被称作“并发对象”。线性一致性(Linearizability)使得我们可以使用抽象数据类型来正确的描述并发对象,它还允许了较高的并行度,并允许开发者利用串行领域已知的技术,对并发对象进行阐述和推理。线性一致性给我们这样一种错觉:并发进程的每一个操作是在调用和响应之间的某个点瞬时生效的。这也意味着并发对象的操作可以使用“先决条件”(pre-conditions)和“后置条件”(post-conditions)来定义。这篇论文定义了线性一致性,并将它和其他的正确性条件比较。论文还提供了一种方法,用来证明实现的正确性,并展示了如何对可线性化的并发对象进行推理。

引言

概述

通俗的来说,一个并发系统由一系列串行进程构成,这些进程通过一些共享的类型对象相互交流。这个模型包含了基于消息传递的架构(共享对象一般是消息队列),以及基于共享内存的架构(共享对象一般是内存中的数据结构)。每个对象都有一个类型(type),这个类型定义了一些可能的值(values)和一些原子操作(operations),这些操作是用来创建和处理对象的唯一途径。在一个顺序系统中,一个对象的操作是被单一进程一个接一个调用的,操作可以用先决条件和后置条件来定义。然而,在并发系统中,一个对象的操作可能被多个进程调用,此时,操作之间可能会有交叉,定义清楚这些交叉是很有必要的。

一次并发计算如果“等同于”一次合法的顺序计算(在第二章,这个“等同于”在某种意义上是被正式定义了的),则是线性一致的。我们将数据类型(顺序的)的公理描述(axiomatic specification)解释为仅允许可线性化的交叉。做了这样的解释后,线性一致性就可以利用抽象数据类型的语义。线性一致性允许较高的并行度,并允许开发者利用标准的检验技术来阐述和推理并发对象。不像其他的正确性条件,诸如顺序一致性(sequential consistency)或是可串行化(serializability),线性一致性是一个局部属性(local property):如果每个独立的对象是可线性化的,那么这个系统就是可线性化的。局部性加强了模块性和并发性,因为对象可以被独立的实现和验证,并且运行时的调度可以完全的去中心化。线性一致性还是一个非阻塞(nonblocking)的属性:进程在调用完全定义(totally-defined)的操作时,不必非得等待。非阻塞加强了并发性,并意味着线性一致性对于那些对实时响应要求苛刻的系统来说,是一个合适的条件。线性一致性是一个简单、直观、有吸引力的正确性条件,它归纳统一了若干的正确性条件。本文会显式或隐式的提及这些条件。

axiomatic specification:感觉有一种本身的自然属性的含义,“将数据类型(串行的)的公理描述(axiomatic specification)解释为仅允许可线性化的交叉”更像是在说“将仅允许可线性化交叉作为本文讨论的数据类型的一种自然属性”)

基于公理描述和线性一致性,我们可以推导下面两类问题:

  1. 我们可以将*表示不变量(representation invariant)抽象函数(abstraction function)*推广到并发领域,推导可线性化对象实现的正确性;

不知所云。这里提及“表示不变量”和“抽象函数”,好像是在讲,下文中很多的符号化定义,在并发领域中用起来,也是有理论支撑的。
representation invariant的大概含义就是开发者对于其所要实现的对象的正确性声明。详细解释如下: Another way to ensure correct code within a class is to make explicit any restrictions on what values are allowed to be in a private instance variable, and any restrictions on relationships between values in different instances variables in our object. Or put another way, making sure you know what must be true about our object representation when it is in a valid state. These are called representation invariants. Representation invariants are statements that are true about the object as viewed by the implementor.

  1. 我们将并发计算的断言转换成更加简单的顺序计算下的断言,来推导可线性化对象的计算。

第二章展示了我们对并发系统构建的一个模型,以及线性一致性的正式定义。第三章讨论了线性一致性的局部性和非阻塞属性,并和其他正确性条件做了比较。第四章展示了我们验证可线性化对象实现的方法,并实现了一个新颖的高并发队列来阐述这个验证方法。第五章举了几个例子,分析了一下可线性化的并发寄存器和队列。第6章调查了下有关工作,并讨论了线性一致性作为正确性条件的意义。

动机

从直觉上来讲,并发对象的正确性应当需要下面这两个条件:

  1. 每个操作都像是瞬时“生效”的;
  2. 不存在并发关系的操作,要保留其执行的先后顺序。

这两个条件允许我们用“可接受的顺序行为”来描述“可接受的并发行为”,这种方式可以简化并发编程的推理。我们将在下一章正式的讨论这些概念。这里我们用一些例子来说明在考虑“可接受的并发行为”时,哪些是需要的,哪些是不需要的。我们以先入先出(FIFO)队列为例。FIFO队列提供两种操作:Enq入队,Deq出队并删除出队的元素。下图展示了一个FIFO队列在并发的情况下,所出现的四种可能情况。图中,从左到右为时间轴,每个操作都持续了一小个时间段,相互交叉的时间段表示操作是并发的。我们使用 E(x) A 表示进程A入队元素x,用 D(x) A 表示进程A出队x。

图1 FIFO的历史

H1符合我们对FIFO的直观认识。在H1中,进程A和B先并发的入队元素x和y,之后,B出队x,再然后A出队y,并开始入队z。因为x的出队先于y的出队,根据FIFO的特性,x也必须先于y入队。事实上,x和y的入队是并发的,因此,x比y先入队是可能的。H1中,进程A对z的入队并未结束。我们对于那些会持续不断,甚至永远执行的进程,也是有兴趣的。

H2看起来就不能接受了。H2中,很明显可以看到x是在y之前入队的,但是x还未出队,y就先出队了。对于FIFO来说,A出队的应该是x才对。而H3是可以接受的,虽然x的出队动作在x的入队动作还未结束前就发生了。此时,直观上来说,x的入队是在 E(x) A整个操作结束前就生效了的。最后,H4很明显是不可接受的,因为y出队了两次。

当决定一次并发执行是否可接受时,得考虑到对象的既定语义。比如,对于FIFO队列来说可接受的并发行为,就不一定适用于堆栈、集合、字典表一类的结构了。我们再举一个寄存器对象读写例子,这里的寄存器对象符合Misra对并发寄存器的严格定义。我们举的例子就好比是将Misra处理对象的方式范化到了更丰富的操作集上。如下图,H5是可接受的,但H6不是。这两个行为区别于这一点:H5中,B读取到0,而H6中,B读取到的是1。H6不可接受是因为,进程A之前读到了1,这表示“B写1”必须发生在“A读1”之前。而“C写0”,虽然和“B写1”是并发的,但却是在“A读1”之后的。

图2 寄存器的历史

在下一章中,我们会定义线性一致性的概念。这个概念会涵盖本章讨论的所有可接受的执行历史,并将本章直观上讨论的东西形式化。

系统模型和线性一致化定义

执行历史

一般,一个并发系统由通过共享数据结构(称之为对象)相互通信的进程构成。每个对象都有一个唯一的名字和类型。类型定义了这个对象可能的取值,还定义了一系列基本操作,这些操作是处理该对象的唯一途径。进程是顺序的:每个进程都会顺序的执行对象的一系列操作,也就是一个接一个的调用操作并接受对应的响应(在这个系统中,动态创建的子线程可以被简单的认为是一个在fork之前(和join之后)没有任何操作的独立线程)。

正式来讲,一个并发系统的一次执行可以用*历史(history)来表示。历史是一个由调用(invocation)和响应(response)构成的有限序列。一个历史H的子历史(subhistory)*是H所有事件的一个子序列。操作的调用被记为 < x op(args*) A> ,其中,x是对象名称,op 是操作名称,args* 表示参数值,A则是进程名。操作的响应被记为 <x term(res*) A>,其中,term表示结束条件,res* 是返回结果。我们使用“Ok”表示正常的结束。如果一个响应和一个调用的对象名和进程名一致,那么这个响应配对这个调用。如果在历史中,一个调用没有配对的响应,那么这个调用就挂起了。如果H是一个历史,complete(H) 就表示H的最大子序列,最大子序列仅由调用和配对的响应构成。

一个历史H是有序的,如果

  1. H的第一个事件是一个调用;
  2. 每个调用,除了最后一个,后面都紧接着一个配对的响应。每个响应也都紧接着一个调用。

一个无序的执行历史是并发的。 一个进程子历史(process subhistory),H|P(H at P),是一个历史H中,进程P的所有事件构成的子序列。一个对象子历史(object subhistory),H|x 的定义类似。 对于两个历史H和H',如果对于每个进程P,都有 H|P = H'|P,则H和H' 被认为是相等的。 如果H的每个进程子历史 H|P 都是有序的,那么H就被认为是结构良好的(well-formed)。本文假设讨论的所有历史都是结构良好的。需要注意的是,对于一个结构良好的历史,其进程子历史的有序性是必要的,但对象子历史却无此必要。

一个操作,e,由一对调用(inv(e))和响应(res(e))构成。我们用 [q inv/res A] 来表示操作,其中,q是操作的对象,A是执行操作的进程。对于操作e0和e1,如果inv(e1)先于inv(e0),并且res(e0)先于res(e1),则说e0被e1包含。像 <x op(args) A> 和 [q inv/res A] 这类符号中的尖括号和方框号,在不混淆的情况下将被省略,其中的对象和进程名,在上下文十分清晰时,也可能被省略。

举个例子,上文中的H1,对于FIFO队列q来说,就是一个结构良好的历史。

  q Enq(x) A
  q Enq(y) B
  q Ok() B
  q Ok() A
  q Deq() B
  q Ok(x) B
  q Deq() A
  q Ok(y) A
  q Enq(z) A
复制代码

H1中的第一个事件是Enq的调起(参数x,进程A),第四个事件则是配对的响应(结束条件是Ok),无返回结果。[q Enq(y)/Ok() B] 被 [q Enq(x)/Ok() A]包含。子历史complete(H1),是将最后一个挂起的Enq删除后的H1。将头两个事件颠倒一下顺序,就变成了和H1相等的另一个历史。

一个历史集合S,对于任何在集合S内的历史H,如果H的每个前缀也在也在集合S中,那么,这个历史集合就被称作是前缀闭合的(prefix-closed)。 一个对象的所有事件构成的历史,被称作单一对象历史(single-object history)。 一个满足前缀闭合的单一对象历史的集合,被称作该对象的有序规范(sequential specification)。 对于一个历史H,如果每个对象子历史 H|x 都属于x的有序规范,那么这个有序历史H被称作是合法的。 有很多常见的技术可以用来定义有序规范。本文,我们将使用Larch的公理类型(axiomatic style),我们将用一个表征对象状态(在历史结束时的状态)的值来描述对象的有序历史,这些值将用在对象操作的前置条件(preconditions)和后置条件(postconditions)中。举个例子,下图是FIFO队列Enq和Deq的公理化说明。Enq的后置条件声明了,在操作结束时,新队列等于旧队列加上插入的e。Deq的定义声明了在非空队列上执行该操作,将会移除最先入队的元素。一个操作,比如Enq,如果对于对象所有可能的取值都有明确的定义,那么这个操作就被称作是完全的(total),否则就是局部的(partial)。比如下图中的Deq就是局部的,它没有为队列为空的情况做定义。

图3 队列操作的公理说明

线性一致性的定义

一个历史H引出了在操作上的一个非自反的偏序关系<_H
e_0 <_H e_1\ if\ res(e_0)\ precedes\ inv(e_1)\ in\ H.(如果e_0的响应在e_1的调用之前)。
(适当的时候,偏序关系中的小标会被省略)。不严谨的说,<_H捕获了历史H中所有在真正的时间上有先后关系的操作(译者注:原文是the "real-time" precedence ordering).那些不符合<_H偏序关系的操作就被称作是并发的。如果H是顺序的(译者注:感觉“顺序的”就有点等同于“串行的”意思了),那么<_H就是全序的。

一个历史H是可线性化的,如果它可以扩展(通过添加0个或是更多的响应事件)到其他的一些H',并且:

  • L1:complete(H')等同于某个合法的顺序历史S
  • L2<_H \subseteq <_S (这个条件的意思就是顺序历史S保留了历史H里所有的偏序关系<_H

不严谨的说,将H扩展到顺序的H'传达了这样一个观念:一些仍在执行(或是挂起)的操作,可能其响应还没返回给调用者,该操作要干的事情,就已经在执行过程中的某个点生效了(比如上文历史H3中挂起的Enq)。而我们只关注complete(H')则传达了这样一个观念:剩下的仍在执行的调用都还未生效。L1表明了虽然有些操作是交叉的,但每个操作却都像是以一个未分割的整体来执行的(interleaved at the granularity of complete operations)。L2表明了这个顺序执行S,是会保留操作在真实时间上的一些先后关系的。

我们将S称作H的一个线性化。线性一致性本身也存在一些不确定性:

  1. 对于H,满足L1L2的H'可能会有多个;
  2. 对于H',也可能存在多个线性化S。 一个对象,如果其所有的并发历史,在满足一些顺序规范的前提下,是可线性化的,那么这个对象就是一个可线性化的对象。

重温队列

我们用“\centerdot”表示事件的连接。上文中的历史H1是可线性化的,因为 H1\ \centerdot\ <q\ Ok()\ A>(就是在H1后面再加个<q Ok() A>) 等同于下面的顺序历史H1':

q Enq(x) A
q Ok() A
q Enq(y) B
q Ok() B
q Deq() B
q Ok(x) B
q Deq() A
q Ok(y) A
q Enq(z) A
q Ok() A
复制代码

H2不是可线性化的

q Enq(x) A
q Ok() A
q Enq(y) B
q Deq() A
q Ok() B
q Ok(y) A
复制代码

因为x的入队是y入队之前完成的,但是y却比x先出队了。

线性一致性并不排斥类似H3这类的历史,其中,有个操作在其响应前就“生效”了:

q Enq(x) A
q Deq() B
q Ok(x) B
复制代码

H3可以扩展到H3' = H3\ \centerdot\ <q\ Ok()\ A>,就可以等同于入队在出队之前的顺序历史了。

最后,我们来看H4。

q Enq(x) A
q Enq(y) B
q Ok() A
q Ok() B
q Deq() A
q Deq() C
q Ok(y) A
q Ok(y) C
复制代码

H4不是可线性化的,因为y只入队了一次,却出队了两次,因此H4没法等同于任何一个FIFO队列的顺序历史。

线性一致性的特性

本章将证明线性一致性是一个局部非阻塞的属性,并讨论它和其他正确性条件的不同。

局部性

如果一个并发系统中的每个对象满足都属性P,则该并发系统也满足属性P,那么,这个属性P就是一个局部属性。线性一致性就是一个局部属性:

定理1. H是可线性化的,当且仅当,对于每一个对象x,H|x是可线性化的。

证明

“仅当”部分是显而易见的。

“仅当”部分应是指:如果H是可线性化的,那么对于每一个对象x,H|x是可线性化的。下面的部分则在证明:如果对于每一个对象x,H|x是可线性化的,那么H是可线性化的。

对于每一个x,选取一个线性化的H|x。 令R_x为一系列响应的集合,这些响应添加到对应的H|x后,使得H|x是线性化的。 令<_x表示了H|x上的线性化顺序。

这里,<_x和上文中定义的<_H不是一个含义。<_x<_H的关系或可用下图表示:

图4 <x和<H的关系
图中三个区域的含义如下:

  • 区域1. H|x若要等同于一个合法的顺序历史,则H|x中的并发操作需要满足的顺序关系。
  • 区域2. 在仅和x有关的操作中,某个操作的响应在另一个操作的调用之前。
  • 区域3. 也表示某个操作(设为A)的响应在另一个操作(设为B)的调用之前,但A、B这两个操作中,至多只有一个是和x有关的操作。

令H'是将R_x添加到H后形成的历史。 我们将在complete(H')的所有操作上构建一个偏序关系 < ,使得:

  1. 对于每个x,<_x  \subseteq <
  2. <_H \subseteq <

令S是将complete(H')中的所有操作按照偏序关系 < 排序后所构成的顺序历史。条件(1)意味着S是合法的,因此L1是满足的。条件(2)则意味着L2是满足的。

条件(1):我们在上文中“令<_x表示了H|x上的线性化顺序”,这句话的另一个含义就是“将H|x中的操作按照<_x依次排列后,得到的顺序历史S|x,是合法的”。每个S|x都是合法的,所以S是合法的。

令 < 是 <_x<_H并集的传递闭包。这样的一个 < 就满足了条件(1)和条件(2)的关系。但是,< 是否偏序仍然有待证明。

传递闭包在这里的大概意思就是,包含了关系<_x<_H,并且包含了可由<_x<_H所能推导出来的所有关系。这样定义的关系 < 可能包含一些既不存在于<_x,也不存在于<_H,而是由<_x<_H推导出来的关系,如下图所示:

图5 由$<_x$和$<_H$推导出来的关系
图中, e_1(x)e_2(x)是和对象x有关的操作, e_3(y)是和对象y有关的操作。 因为 e_1(x)的响应在 e_3(y)的调用之前,我们有 e_1(x) <_H e_3(y)。 我们令 e_2(x)e_1(x)更早生效,才是合法的,则有 e_2(x) <_x  e_1(x)。 根据以上两个关系可以推出: e_2(x) < e_1(x)。这个关系并不在 <_x中,也不在 <_H中。 而因为引入了新的关系,那么,< 是否依然是偏序的呢?

我们将用反证法来证明 < 是偏序的。 如果 < 不是偏序的,那么就存在这样一组操作e_1,...,e_n,满足e_1 < e_2 < ... < e_ne_n < e_1,并且,这其中的每一个关系,要么是<_x,要么是<_H。选择一个长度最小的环。

本文的偏序关系 < 是反自反的偏序(也可以说是严格偏序),下面是百度百科中对于反自反偏序的定义:
给定集合S,“<”是S上的二元关系,若“<”满足:
反自反性:∀a∈S,有a≮a;
非对称性:∀a,b∈S,a<b ⇒ b≮a;
传递性:∀a,b,c∈S,a<b且b<c,则a<c;
则称“<”是S上的严格偏序或反自反偏序。
< 是 <_x<_H的传递闭包,反自反和传递性是显而易见的。此处的反证法在证明,< 也满足非对称性。

假设环上的所有操作都是和对象x有关的,因为<_x是一个全序(对于任何两个和x有关的操作,都可以用<_x比较),所以必须存在两个操作e_{i-1}e_i,满足e_{i-1} <_H e_i,并且满足e_i <_x e_{i-1},这是矛盾的,违反了x的线性一致性。

那么,这个环得包含至少两个对象的操作。 我们假设找到的长度最小环是e_1 < e_2 < ... < e_ne_n < e_1。令e_1是对象x的操作,并令e_2不是对象x的操作,以保证环至少包含两个对象。 接下来,我们依然是用反证法,来推导出两个矛盾的结论。

  1. 长度最小环必须满足e_2, ..., e_n都不是对象x的操作
    假设e_3, ..., e_n中有对象x的操作,并且第一个和x有关的操作是e_i。因为e_{i-1}e_i之间的关系不是<_x,那关系一定是<_H,有e_{i-1} <_H e_i也即e_{i-1}的响应是在e_i调用之前的)。
    e_2的调用则是在e_{i-1}的响应之前的,因为如果不是这样,而是e_{i-1} <_H e_2,则这会导致一个更小的环e_2, ..., e_{i-1}
    最后,我们还有e_1 <_H e_2e_1的响应是在e_2的调用之前的。 根据上面的三个响应调用的顺序(加黑部分),我们有e_1的响应是在e_i的调用之前,因此有e_1 <_H e_i,这导致了更小的环e_1, e_i, ..., e_n
    也就是说,如果e_3, ..., e_n中有对象x的操作,则我们总是能找到一个更小的环。
    也就是说,长度最小环必须满足e_2, ..., e_n都不是对象x的操作。
  2. 长度最小环必须满足e_n是对象x的操作。
    我们假设e_n不是对象x的操作,因为有e_n < e_1,那么,必然是e_n <_H e_1。又有e_1 <_H e_2,并且因为<_H是可传递的,因此有e_n <_H e_2,这导致了更小的环e_2, ..., e_n
    也就是说,长度最小环必须满足e_n是对象x的操作。

从上面可以看到,根本没法找到长度最小环,也就是<是满足非对称性的。所以,< 是偏序关系。

因此,在考虑某个历史的线性一致性时,我们只需要考虑所有单个对象的历史就可以了。

局部性是很重要的,因为这让我们可以用模块化的方式来设计和构建并发系统,可线性化的对象可以独立的实现、验证和运行。一个基于非局部性属性的并发系统,要么必须依赖一个中心调度器,用来调度所有的对象,要么就得给对象加上额外的限制,以保证他们能够和谐共处(原文是:follow compatible scheduling protocols)。局部性并非理所当然的,下文中就讨论了其他非局部的正确性条件。

阻塞vs非阻塞

线性一致性是一个非阻塞的属性:一个完全操作的尚未结束的调用不必等待另外一个尚未结束的调用完成。

原文:a pending invocation of a totally-defined operation is never required to wait for another pending invocation to complete. 注意这里的totally-defined operation(或是下文中出现的total operation),在上文是有定义的:如果对于对象所有可能的取值都有明确的定义,那么这个操作就被称作是完全的(total)。 这里的pending,感觉更多应该是尚未结束,结果待定之意,不论是因为挂起了,还是因为仍在执行中。

定理2. 令inv是一个完全操作的调用,如果 是一个可线性化历史H中仍在执行的调用,那么就存在一个响应 <x res P> 使得H\ \centerdot\ <x\ res\ P> 是可线性化的。

证明

令S是H的某个线性化顺序历史。 如果S已经包含了 <x inv P> 对应的响应 <x res P>,那么证明就完成了,因为S也必定是 H\ \centerdot\ <x\ res\ P> 的线性化顺序历史。

注意,H是不包含<x res P>的,根据线性一致性的定义,这个<x res P>是在由H扩展到H'时加入的。

如果S不包含<x inv P>(因为根据定义,线性化的顺序历史是不包含任何尚未结束的调用的)。因为操作本身是完备的,我们可以找到一个响应 <x res P>,使得:S'=S\centerdot<x\ inv\ P>\centerdot<\ x\ res\ P>是合法的,同时是H \centerdot<x\ res\ P> 的一个线性化顺序历史,也是H的一个线性化历史。

H是包含<x inv P>的,S如果不包含<x inv P>,根据线性一致性的定义,这个应是在complete(H')这一步去掉的。

这个定理意味着,如果一个进程中,有一个尚未结束的调用(且是某个完全操作的调用),线性一致性本身是不会强制阻塞这个进程的(译者注:感觉只有这个调用是因为挂起而未结束,这句话才实际意义)。当然,阻塞(甚至是死锁)在某些线性一致性的实现中是可能出现的,但是这并不影响“非阻塞”这个属性本身的正确性(可线性化对象的非阻塞实现在别的地方有所讨论)。这个定理说明,线性一致性在那些并发和实时响应很重要的系统中是一个比较合适的正确性条件。下文中我们也会看到其他的一些正确性条件,比如顺序一致性,就没有非阻塞的属性了。

非阻塞性也并非完全排斥阻塞行为,在某些场景中,这些阻塞行为是被有意设计出来的。比如,一个进程如果想要从一个空队列上出队,那么阻塞这个进程就是有意义的,该进程必须等到其他进程入队一个元素才能继续执行。上文中,我们通过为Deq制定了一个不完全的规范(未定义队列为空时行为),从而规避了这个特殊情况。对一个不完全的有序规范,从并发角度来解释的话,最自然的说法就是一直等待,直到对象达到一个有定义的状态。(译者注:原文是The most natural concurrent interpretation of a partial sequential specification is simply to wait until the object reaches a state in which the operation is defined.)

这个“非阻塞”到底是在说啥呢?我觉得是这样。 比如说如下图的两个操作。我们令e_1(x)要在e_2(x)之前生效,对x的操作才是合法的。

图6 挂起的操作
不过,我们照样可以 让 e_2(x)的响应在 e_1(x)之前,同时保证其线性一致性,从而得到下图。
图7 完全操作
虽然我们要求 e_1(x)要在 e_2(x)之前生效,但是 e_2(x)仍然不必等待 e_1(x)的完成,自己就可先完成了。因为“生效”和“完成”(或者到达“响应”这个点)这两件事,根本是不同的。我们可以选择在时间线上的任意一点“生效”。

和其他正确性条件的比较

Lamport提出的顺序一致性,要求一个历史等同于一个合法的顺序历史。顺序一致性比线性一致性的要求要弱,因为它并不要求保留原始历史中的优先顺序(译者注:应是指<_H部分)。举个例子,历史H7是顺序一致的,但不是可线性化的。

q Enq(x) A
q Ok() A
q Enq(y) B
q Ok() B
q Deq() B
q Ok(y) B
复制代码

这里可以将H7理解为有严格先后顺序的历史,也就是没有并发。 按照上文的说法,对于顺序一致性来说,可以调整各个操作之间的先后顺序。那我们可以将H7调整为如下的顺序,就是合法的了。

q Enq(x) A
q Ok() A
q Enq(y) B
q Ok() B
q Deq() B
q Ok(y) B
复制代码

而,对于线性一致性来说,不可以调整这种先后顺序,H7是不合法的。

顺序一致性不具有局部性。我们来看历史H8,A和B两个进程操作队列p和q。

p Enq(X) A
p Ok() A
q Enq(y) B
q Ok() B
q Enq(x) A
q Ok() A
p Enq(y) B
p Ok() B
p Deq() A
p Ok(y) A
q Deq() B
q Ok(x) B
复制代码

H8|p 和 H8|q 都是顺序一致的,但H8不是。

起初看到这时,我以为线性一致性是顺序一致性的升级版。后来看了下顺序一致性的论文。 看完之后,发现顺序一致性是在讨论该如何协同多核和内存的工作,是在讨论底层硬件设计问题。而本文的线性一致性,则更多的像是在讨论并发编程问题。顺序一致性做的是底层工作,而线性一致性做的是应用层工作。将这两者拿来进行比较,并讨论谁的要求更严格,我总觉有点谜。
与其说,线性一致性是顺序一致性的升级,倒不如说顺序一致性是线性一致性的基础。

猜你喜欢

转载自juejin.im/post/5d328b94e51d45554877a698
今日推荐