Answers:
考虑并发的,无等待的(或无锁的,较弱的)数据结构的设计。在这种情况下,通常需要线性化,即使在某些情况下,可以通过满足较弱的正确性条件来提高性能和可伸缩性。满足这种弱条件的实现是否有用通常取决于应用程序。相反,线性化的实现始终可用,因为设计人员可以将其视为原子化的。
此外,线性化性是非阻塞属性:完全不需要阻塞(为所有对象状态定义)总操作。而是,可序列化性不是非阻塞属性。因此,为了提高并发程度,并发数据结构的设计人员始终依赖于线性化。
在过去的15年中,我已经多次重读Herlihy和Wing。这是很难读的。这是不幸的,因为尽管有些微妙之处,但基本思想实际上还是很合理的。
简而言之:线性化性类似于可序列化性,但是附加的要求是序列化必须遵守事务之间的其他排序约束。目的是使您能够对单个原子数据结构进行严格推理,而不必一次对整个系统进行推理。
线性化也很容易实现:只需将互斥锁与要线性化的对象关联即可。该对象上的每个事务都从锁定互斥锁开始,并通过解锁互斥锁结束。
这是我将使用的定义:
一种系统是serializabile如果给定一组记录,对一组数据的,好像执行交易的任何结果是相同的交易是在一些顺序每个事务中的操作的顺序包含它们的事务内执行,并由交易代码指定。
可序列化性不允许出现在不同事务之间进行操作交错的情况,并且要求选定的事务顺序满足因果关系(如果事务A写入值x,事务B读取值A写入的值x,则事务A必须先于事务B但是,它对事务的顺序没有任何其他限制(特别是它没有说明流程以及流程感知事件的顺序)。
还有另一个相关的想法,它确实增加了有关流程执行操作的顺序的约束(但仅涉及单个读/写操作而不涉及事务):
如果任何执行的结果与所有进程的操作均按某个顺序执行,并且每个单个进程的操作按其程序指定的顺序按此顺序出现,则系统是顺序一致的。(Lamport,“如何使多处理器计算机正确执行多进程程序”,IEEE T Comp 28:9(690-691),1979)。
顺序一致性定义中的隐含含义是,我们仅接受顺序顺序,其中对于每个内存位置(对象),所诱导的操作顺序顺序应遵循以下规则:每个读取操作返回到位置x
的值必须与写入的值相同紧接在前的写入操作x
按顺序进行。
线性化具有以下良好意图:(a)将事务(来自序列化)的概念与流程期望它们发出的操作按顺序完成的概念(从顺序一致性)结合在一起;以及(b)缩小讨论每个对象的正确性标准隔离对象,而不是强迫您对整个系统进行推理。(我想说的是,即使在存在无法线性化的其他对象的系统中,我的对象的实现也是正确的。)我相信Herlihy和Wing可能一直在尝试严格定义监视器。
(a)部分是“容易的”:类似顺序一致性的要求是,每个进程在对象上进行的事务以程序指定的顺序出现在结果序列中。类似序列化的要求是对象上的事务都是互斥的(可以序列化)。
复杂性来自目标(b)(能够独立于所有其他对象谈论每个对象)。
在具有多个对象的系统中,对对象B的操作可能会限制我们认为在对象A上调用操作的顺序。如果我们查看整个系统的历史记录,则会受到某些顺序的限制,并且将需要拒绝别人。但是我们想要一个可以孤立使用的正确性标准(合理考虑对象A发生了什么,而又不诉诸全局系统历史)。
例如:假设我试图争论对象A(它是一个队列)的正确性,假设对象B是一个内存位置,并假设我具有以下执行历史记录:线程1:A.enqueue(x),A。 dequeue()(返回y)。线程2:A.enqueue(y),A.dequeue()(返回x)。是否有事件交织使队列的这种实现正确?是:
Thread 1 Thread 2
A.enqueue(x) ...
... A.enqueue(y)
... A.dequeue() (returns x)
A.dequeue(y) (returns y) ...
但是现在,如果历史记录(包括对象B)是:B从值0开始。线程1:A.enqueue(x),A.dequeue()(返回y),B.write(1)。线程2:B.read()(返回1)A.enqueue(y),A.dequeue()(返回x)。
Thread 1 Thread 2
A.enqueue(x) ...
A.dequeue() (returns y) ... (uh oh!)
B.write(1) ...
... B.read() (returns 1)
... A.enqueue(y)
... A.dequeue() (returns x)
现在,我们想对“正确性”进行定义,以说这段历史表明我们对A的实现是错误的,对B的实现是错误的,因为没有序列化是“有意义的”(线程2都需要阅读来自B的一个尚未写入的值,或者线程1需要从尚未排队的A的值出队。)因此,虽然我们对A上事务的原始序列化似乎是一个合理的方法,但如果实现的话允许像第二个这样的历史记录,那么显然是不正确的。
因此,线性化添加的约束是非常合理的(即使对于诸如FIFO队列之类的简单数据结构也是必需的。)它们是这样的:“您的实现应禁止dequeue()一个值,直到该值在队列中的某个时间才会被排队()。未来。” 线性化是很容易实现的(自然的):只需将一个互斥锁与您的对象相关联,每个事务就以锁定开始,并以解锁结束。当您尝试使用非阻塞或无锁或无等待技术而不是简单的互斥体来实现原子性时,关于线性化的推理开始变得棘手。
如果您对一些参考文献感兴趣,我发现了以下内容(尽管我认为有关“实时”的讨论是使线性化变得比原来更困难的红人之一。)https:// stackoverflow.com/questions/4179587/difference-between-linearizability-and-serializability
wait()
and 进行的复杂讨论notify()
。线性化提供了一种讨论更复杂/优化的监视器实现的正确性的方法。
Related Work
Herlihy和Wing的论文部分。他们的确提到monitor
了这一说法Our notion of linearizability generalizes and unifies similar notions found in specific examples in the literature
。然而,一个普遍的问题是:线性化的概念是否已在多处理器系统(例如,硬件,编译器,编程语言和并发数据结构)中被广泛采用?(由于目光短浅,我只知道监视器。)如果不是,障碍是什么?最新的技术水平是什么?
首先,线性化和可串行化不能直接比较。如下表所示,主要区别在于,左侧的所有单个操作都是原子操作(就像synchronized
每个操作符周围都有一个Java 。右侧的原子性单位是事务;单个操作不是原子的)这就是为什么Serializability一直是数据库资料的一部分,而左侧一直是处理器内存资料的主题(读/写op是原子的)的原因。原始的键值存储(例如dbm和memcached)从左侧开始(get / put是原子的),但是较新的版本正越来越多地支持事务(例如Google的扳手)。
对象 操作是原子的| 交易是原子的 -------------------------------- + ----------------- ---------------- 线性度| 顺序一致性| 可序列化 因果一致性| 缓存一致性|
线性化要求并发设置中的对象系统的行为必须与在并行Universe中一次处理一个操作(请求/响应对)的顺序系统相同,即(a)客户端在两个宇宙中看到的响应完全相同(b)保留了时间顺序(有关此内容的更多信息,请参见下文)。
像顺序一致性一样,可串行性的定义只需要第一个条件。
时间顺序保留的意思是:如果A:x.op1()(A是一个客户端,x是一个对象,而op1是一个操作)在另一个操作B:y.op2()开始之前完成,则在顺序宇宙中请求以相同顺序处理。顺序一致性(SC)不需要此操作;允许该对象将客户的请求排队,响应客户,然后再对其进行评估。此外,对象可以依次处理来自其他客户端的后续请求,并在到达第一个请求之前对其进行评估。
不保留时间顺序是一个问题。在A:x.op1()之后,假设A拿起电话并告诉B,然后B打电话给x.op2()。由于第二步涉及系统未跟踪的消息,因此系统无法了解事件的因果链。在许多实际情况下,A假设一旦x做出响应,B的调用就可以依赖于更新后的状态,这并非没有道理。如果不保留时间顺序,则A和B会感到惊讶。在线性化系统中不会发生这种情况。
时间顺序保存的第二个不错的特性是局部性和组成性,即由线性化对象构建的系统本身就是线性化的。因此,您可以将其分拆成许多单独的分区,每个分区都由自己的KV-store服务器管理,而不是拥有一个整体式键值存储;如果它们每个都是线性化的,那么整个数据库就可以充当一个线性化的整体式KV存储,而无需付出额外的努力。