2. 计算机科学国家重点实验室(中国科学院 软件研究所), 北京 100190;
3. 中国科学院大学, 北京 100049
2. State Key Laboratory of Computer Science (Institute of Software, Chinese Academy of Sciences), Beijing 100190, China;
3. University of Chinese Academy of Sciences, Beijing 100049, China
高效实现的并发数据结构能够充分利用多核系统的潜力, 提升并发程序的性能. 并发数据结构的抽象行为往往以顺序规约的形式给出, 正确性条件定义了并发数据结构是否符合顺序规约. 并发数据结构领域实际的正确性条件是可线性化(linearizability)[1]. 如果一个并发数据结构中的每个函数的执行都等效于在其调用和返回期间的某个时间点的瞬时执行, 那么该并发数据结构满足可线性化.
已有的可线性化验证的研究大部分假定使用顺序一致性(sequential consistency, SC)内存模型[2]. 在SC内存模型下, 并发程序的执行效果等同于所有进程对内存的访问以某种串行序交织进行, 这个串行序保留了每个进程访问内存动作的序关系, 并且所有对内存的写动作会立即被全局所见. 然而, 当代的多核处理器(如x86/TSO[3], POWER[4]和ARM[5])和编程语言(如C/C++[6]和Java[7])并不遵循SC内存模型, 而是遵循松弛内存模型(relaxed memory model). 为了提升性能, 松弛内存模型相比SC内存模型允许更多的行为. 以TSO(total store order)内存模型[3]为例, 它为每个处理器关联了一个先入先出的存储缓冲区(store buffer), 使得进程对内存的写动作不再立即被全局所见. 尽管在每个实现TSO内存模型的多处理器系统中存储缓冲区的大小是固定的, 为了描述TSO内存模型下任意并发数据结构的实现, 文献中提出的TSO内存模型[8−11]往往为每个处理器关联一个大小不限界的先入先出存储缓冲区; 否则, 使用固定大小存储缓冲区的TSO内存模型总是无法建模需要更大存储缓冲区的TSO内存模型实现. 在TSO内存模型中, 在执行写动作时, 处理器将待更改的内存单元及其新值先放入对应的存储缓冲区中, 然后在将来某个不确定时刻将缓存的写动作从存储缓冲区中刷出(flush), 并修改对应的内存单元.
之前的文献已经提出了若干可线性化在松弛内存模型下的变种, 例如TSO内存模型下的TSO-to-TSO可线性化[11]、TSO-to-SC可线性化[12]和TSO可线性化[13]、C++11内存模型下的两个可线性化变种[14]以及C11内存模型下的一个可线性化变种[15]. TSO-to-TSO可线性化要求当函数调用或函数返回发生时, 进程额外向存储缓冲区放入一个标记; 当标记离开存储缓冲区时, 会引发调用刷出(flushCall)或返回刷出(flushReturn)动作. TSO-to-TSO可线性化使用扩展历史, 即函数调用、函数返回、调用刷出和返回刷出动作的序列, 来表示并发数据结构的行为. 扩展历史记录了并发数据结构和客户程序以及存储缓冲区的交互. TSO-to-SC可线性化只使用函数调用和函数返回动作的序列来表示并发数据结构的行为; TSO可线性化本质上只使用函数调用和返回刷出动作的序列来表示并发数据结构的行为. TSO内存模型下可线性化的这3个变种都可理解为: 在保留特定动作之间的序的前提下, 一个并发数据结构的行为是否可以变换到另一个并发数据结构或者顺序规约的行为. 这3个变种存在一定的内在联系, TSO-to-SC可线性化和TSO可线性化关注的动作种类是TSO-to-TSO可线性化关注的动作种类的子集, TSO-to-SC可线性化和TSO可线性化定义中需要保持的序也是TSO-to-TSO可线性化定义中需要保持的序的子集. 换句话说, TSO-to-TSO可线性化在这3个变种中是最“难”的. TSO可线性化和TSO-to-TSO可线性化也有着显著的区别: 前者定义在并发数据结构和顺序规约之间, 而后者定义在并发数据结构之间.
可线性化的验证问题有着固有的困难性. SC内存模型下的可线性化问题一般是不可判定[10], 而在进程数目固定时是可判定的[16]. 迄今为止, 松弛内存模型下可线性化的可判定性结果还很少. 我们已经证明了进程数目固定时, TSO-to-TSO可线性化仍是不可判定的[17], 而k-限界TSO-to-SC可线性化是可判定的[18]. 然而进程数目固定时, 限界的TSO-to-TSO可线性化和限界的TSO可线性化的可判定性仍是未知的.
本文提出了k-限界TSO-to-TSO可线性化和k-限界TSO可线性化, 它们是TSO-to-TSO可线性化和TSO可线性化的限界版本, 只考虑限界数目的函数调用、函数返回、调用刷出和返回刷出动作序列. 需要注意的是, TSO内存模型下对可线性化的这3个限界版本的验证问题仍然是非平凡的. 因为读、写、刷出和cas(compare-and-swap)动作的数目是不限的, 因此使用的存储缓冲区大小是无界的, 这导致对应的操作语义是无穷状态迁移系统.
基于TSO内存模型下可线性化的3个变种间的联系, 为了以统一的方式验证TSO内存模型下可线性化的3个限界版本, 我们将TSO内存模型下两个并发数据结构是否符合k-限界TSO-to-TSO可线性化和k-限界TSO-to-SC可线性化以及并发数据结构和顺序规约之间是否符合k-限界TSO可线性化的问题归约为验证两个限界长度扩展历史集合是否满足TSO-to-TSO可线性化的问题, 从而以一种统一的方式对TSO内存模型下可线性化的这3个限界版本进行验证, 并最终证明了可线性化的3个限界版本是可判定的.
具体来说, 我们将验证两个并发数据结构的k-限界TSO-to-TSO可线性化问题归约为验证两个并发数据结构的k-扩展历史集合的TSO-to-TSO可线性化问题. 我们称长度不超过k的扩展历史为k-扩展历史. 一个并发数据结构的k-扩展历史集合, 是指这个并发数据结构的执行对应的长度不超过k的扩展历史的集合. 我们需要计算TSO内存模型下一个并发数据结构的k-扩展历史集合, 计算方法如下: 假定已经有了一种方法来判定TSO内存模型下一个并发数据结构是否有一条给定的k-扩展历史, 然后, 由于可能的k-扩展历史的数目是有限的, 枚举列出所有可能的k-扩展历史, 并使用这个方法来判定每一条可能的k-扩展历史是否属于这个并发数据结构. 我们将验证两个并发数据结构的k-限界TSO-to-SC可线性化问题归约为两个只包含函数调用和函数返回动作的限界扩展历史集合的TSO-to-TSO可线性化问题. 我们将验证并发数据结构k-限界TSO可线性化到正则顺序规约这个问题归约为若干个限界长扩展历史集合间的TSO-to-TSO可线性化问题.
两条k-扩展历史是否符合TSO-to-TSO可线性化是可判定的, 判定方法是: 根据TSO-to-TSO可线性化的定义, 枚举它们之间的一一映射函数并判断是否保留了特定动作之间的序. 两个k-扩展历史集合是否符合TSO-to-TSO可线性化也是可判定的, 判定方法为: 对第1个集合中的每个扩展历史, 寻找第2个集合中是否有与其满足TSO-to-TSO可线性化关系的扩展历史. 因此, 我们给出了判定TSO内存模型下可线性化的3个限界版本的方法.
在上述验证过程中, 只剩下判定TSO内存模型下一个并发数据结构是否有一条给定的k-扩展历史的方法未叙述, 现在给出判定方法. 受到文献[8]的启发, 我们希望将并发数据结构是否有一个特定的k-扩展历史这个问题归约到易失通道机器(lossy channel machine)的控制状态可达问题, 而后者是可判定的[8]. 然而, 文献[8]中的归约方法在这里并不适用, 因为它对应的并发系统中只包含内部、读、写和cas动作, 而不包含函数调用和函数返回动作. TSO-to-TSO可线性化中的一个函数调用或函数返回动作需要同时完成两个任务: 第1个任务是将一个函数调用或函数返回标记插入对应进程的存储缓冲区, 而第2个任务是修改对应进程的控制状态. 证明中的关键步骤是修改内存模型和并发系统的操作语义, 将原本的一个函数调用或函数返回动作转化为一对写动作和cas动作. 修改后的并发系统与原来的并发系统有着相同的扩展历史集合, 而且更易于使用归约的思想判定其是否有一条给定的k-扩展历史.
对一个函数调用或函数返回动作的转化过程如下: 首先引入一个新的内存单元zf, 并将一个函数调用或函数返回动作分解为一个“纯粹”的函数调用或函数返回动作和一个对zf的写动作. 前者只会修改进程的控制状态, 而后者写入一个函数调用标记或函数返回标记. 我们修改了并发系统的操作语义, 使得当zf的写被刷出到内存时, 依据这个写的值是函数调用标记还是函数返回标记, 发出一个调用刷出或返回刷出动作. 因此, 在原TSO-to-TSO可线性化定义中的函数调用和函数返回动作的第1个任务被对zf的写动作和对应的调用刷出和返回刷出动作所模拟.
可以使用文献[18]工作中的思想来处理这些“纯粹”的函数调用和函数返回动作. 我们需要每个“纯粹”的函数调用或函数返回动作都能够立刻被其他进程所见, 为此, 修改操作语义, 为每个函数调用和函数返回动作“绑定”一个特定的cas动作. 具体来说, 引入了一个新的内存单元zw和一个额外的“观察者”进程, 后者以非确定猜测的方式持续地使用cas语句更新zw. 我们修改了内存模型, 使得观察者进程的每一个cas动作之后都立刻伴随一个函数调用或函数返回动作. 通过这些方法, 扩展历史中的函数调用和函数返回动作与zw的cas动作相对应, 调用刷出和返回刷出动作与zf的“刷出”动作相对应.
对并发数据结构的每一条扩展历史, 在修改后的操作语义中存在着一条有着相同扩展历史的执行, 且后者最终清空了每个进程的存储缓冲区, 我们称这样的执行为对应扩展历史的标记见证. TSO内存模型下并发数据结构有一条特定的扩展历史, 当且仅当在修改后的操作语义上存在这个扩展历史的标记见证. 给定一条扩展历史eh, 我们构造一个易失通道机器
最后证明在TSO内存模型下判定可线性化的这3个限界版本的复杂度都在递归函数的Fast-Growing层级[19]
● 相关工作
SC内存模型上的可线性化验证问题得到了学术界的广泛关注[10, 16, 21−25]. 在可判定性方面, Alur等人[16]证明了进程数目固定时可线性化问题是可判定的, 而Bouajjani等人[10]证明了在一般情况下可线性化问题不可判定. 在验证方面, 研究者们提出了多种验证可线性化的方法: Liang等人[21, 22]使用定理证明方法手工证明了一些典型的并发数据结构满足可线性化; Bouajjani等人[23, 24]将可线性化的验证归约到自动机的可达问题; Liu等人[25]通过模型检测的方式验证并发数据结构是否满足可线性化.
关于TSO内存模型下可线性化的研究工作目前较少. 在可判定性方面, 文献[17]中证明了TSO-to-TSO可线性化问题在进程数目固定时已经不可判定, 文献[18]证明了k-限界TSO-to-SC可线性化问题在进程数目固定时是可判定的. 在验证方面, Derrick等人[26]和Travkin等人[27]使用定理证明器KIV验证了一些并发数据结构满足TSO内存模型下的可线性化.
Atig对TSO内存模型下有穷状态并发程序的安全性和活性的已知可判定性问题进行了综述[28]. 一般而言, 并发数据结构的正确性和活性是在函数粒度, 基于函数调用和函数返回动作定义的, 而文献[28]中的安全性和活性关注的是并发程序的特定状态, 这两者的粒度不同. 文献[17, 18]受到了Atig等人[8]工作的启发, 他们揭示了TSO内存模型下并发系统和易失通道机器的联系, 将并发系统的状态可达问题归约到易失通道机器的控制状态可达问题.
文献[18]考虑对k-限界TSO-to-SC可线性化的验证, 每个函数调用或函数返回动作只修改进程的控制状态. 在本文中, 我们考虑的情况更加复杂, 每个函数调用或函数返回动作同时完成两个任务. 文献[18]中对k-限界TSO-to-SC可线性化的结论是本文的一个推论.
2 并发系统本节引入了并发数据结构、客户程序、最一般客户(most general client)和并发系统的概念, 并引入了并发系统的两个操作语义, 即文献[11]中定义的TSO-to-TSO可线性化时使用的操作语义以及在SC内存模型下的操作语义.
2.1 基本概念我们用l=α1·α2·…·αk表示字母表Σ上的一个有穷序列l, 这里, ·是连接符号, 且每个元素αi(1≤i≤k)都属于字母表Σ. 令|l|和l(i)分别表示有穷序列l的长度和第i个元素, 即|l|=k且l(i)=αi. 令l↑Σ′为有穷序列l在字母表Σ′上的投影. 给定函数f, 令f[x: y]为这样的一个函数: 除了将x映射为y之外, 对其他元素的映射都和f一样.令ε为空序列, _表示某个数据项而不关注其值.
一个标号迁移系统(labelled transition system, LTS)是一个四元组A=(Q, Σ, →, q0). 这里, Q是状态(也称为格局)的集合, Σ是迁移标号的字母表, →⊆Q×Σ×Q是迁移关系, 而q0是初始状态. A的一条路径是一个有穷迁移序列
一个并发系统包含着并发运行的客户程序, 这些客户程序之间的交互是通过调用并发数据结构的函数来完成的. 一个并发数据结构提供若干函数以访问该数据结构. 为了简化符号, 假定每个函数有着一个参数并返回一个值. 并发数据结构和客户程序都可以有私有的内存.
令X, M和D是有穷的内存单元集合、有穷的函数名集合和有穷的数据域. 一个原语命令有着如下形式:
$ \tau \mid { read }(x, a) \mid { write }(x, a) \mid { cas\_suc }(x, a, b) \mid { cas\_fail }(x, a, b)|{call}(m, a)| {return}(m, a) . $ |
这里, a, b∈D, x∈X且m∈M. 为了以标号迁移系统的方式定义并发数据结构和客户程序的语义, 我们在原语命令中包含了命令涉及的值. 例如, 读命令read(x, a)中包含了读到的值a. τ是对应内部动作的原语命令. 一个cas(compare-and-swap)命令原子地执行一个读和(可能的)一个写动作. 一个成功执行的cas命令被写为cas_suc(x, a, b), 它仅在x的值为a时执行, 并将x的值修改为b. 一个失败的cas命令被写为cas_fail(x, a, b), 它仅在x的值不为a时执行, 它的执行不会修改任何内存单元的值.
一个并发数据结构L被定义为一个五元组L=(XL, ML, DL, QL, →L). 这里, XL, ML和DL分别是有穷的内存单元集合、有穷的函数名集合和有穷数据域.
一个客户程序C被定义为一个五元组C=(XC, MC, DC, QC, →C). 这里, XC, MC, DC和QC分别是有穷的内存单元集合、有穷的函数名集合、有穷的数据域和有穷的程序位置集合. 令PComC是使用内存单元集合XC、函数名集合MC和数据域DC的原语命令集合, 则→C定义为QC×PComC×QC的一个子集.
一个最一般客户是一个特殊的客户程序, 它不断以非确定的方式调用任意的函数并使用任意的参数. 最一般客户用来展示并发数据结构的所有可能行为. 一个最一般客户MGC被定义为一个五元组(∅, Mc, Dc, {inclt, inlib}, →mgc). 这里, →mgc={(inclt, call(m, a), inlib), (inlib, return(m, a), inclt)|m∈Mc, a∈Dc}是迁移关系. inclt代表当前没有库函数正在执行, 而inlib代表当前有库函数正在执行.
2.3 操作语义让我们使用文献[11]中的思想来给出定义TSO-to-TSO可线性化时使用的操作语义. 假定并发系统C(L)包含n个进程, 每个进程Pi(1≤i≤n)运行一个客户程序Ci=(XC, MC, DC, QC, →C), 而且所有的客户程序使用同一个并发数据结构L=(XL, ML, DL, QL, →L). 这里的C是一个函数, 将每一个进程Pi映射到客户程序Ci. 文献[11]中将C(L)的操作语义定义为一个标号迁移系统
● 每个Conftt中的格局是一个三元组(p, d, u), 其中, p存储了每个进程的当前控制状态, d存储了每个内存单元的取值, 而u存储了每个进程的存储缓冲区的内容;
● Σtt是动作的集合, 包含如下元素:
τ(i)|read(i, x, a)|write(i, x, a)|cas(i, x, a, b)|flush(i, x, b)|call(i, m, a)|return(i, m, a)|flushCall(i)|flushReturn(i).
其中, 1≤i≤n, m∈M, x∈XL∪XC且a, b∈D;
● 对每个进程Pi(1≤i≤n), 迁移关系→tt包含如下迁移: 读动作read(i, x, a)需要确保或者当前进程存储缓冲区u(i)中对应x的最新项是(x, a), 或者这样的项不存在且内存中x的值是a(d(x)=a); 写动作write(i, x, a)并不直接修改内存, 而是将一个数据对(x, a)放入当前进程存储缓冲区u(i)的尾部; 刷出动作flush(i, x, a)从当前进程的存储缓冲区u(i)的头部取出数据对(x, a), 并将内存中x的取值修改为a; cas动作cas(i, x, a, b)会清空当前进程存储缓冲区u(i), 之后, 或者原子地确认内存中x的值a并将其修改为b, 或者原子地确认内存中x的值不为a之后不修改任何内存; 函数调用动作call(i, m, a)将一个标记call放入当前进程存储缓冲区u(i)的尾部, 并开始执行函数m在参数为a时的代码; 函数返回动作retun(i, m, a)将一个标记ret放入当前进程存储缓冲区u(i)的尾部并从函数m中返回, 返回值a并回到客户程序; 当标记call或ret从进程的存储缓冲区u(i)的头部刷出时, 会触发一个调用刷出动作flushCall(i)或返回刷出动作flushReturn(i);
● 初始格局InitConftt是一个三元组(pinit, dinit, uinit), 其中, pinit将每个进程ID映射到一个初始状态, dinit规定了XL∪XC中的内存单元的初始值, 而uinit将每个进程ID映射到一个空的存储缓冲区. 当每个进程都使用最一般客户时, 我们将此时的标号迁移系统
根据文献[12], 并发系统在SC内存模型上的操作语义定义为一个标号迁移系统
在本节中, 我们引入TSO-to-TSO可线性化、(k-限界)TSO-to-SC可线性化和TSO可线性化的定义, 并提出k-限界TSO-to-TSO可线性化和k-限界TSO可线性化的定义.
3.1 (限界)TSO-to-TSO可线性化TSO-to-TSO可线性化[11]是可线性化[1]在TSO内存模型下的一个变种. TSO-to-TSO可线性化满足抽象定理[11, 12, 14], 后者是正确性条件的一个重要属性. 一个正确性条件满足抽象定理, 如果使用并发数据结构时的每个客户程序行为, 在将并发数据结构替换为抽象实现时仍会发生.
在TSO内存模型中, 数据结构和客户程序都可能通过存储缓冲区展示出副作用. 为了确保抽象定理成立, 除了函数调用和函数返回, 并发数据结构还需要记录如下两种时刻: 一个函数写进存储缓冲区的第1个数据项(如果存在), 从存储缓冲区中刷出到内存的时间点; 以及一个函数写进存储缓冲区的最后一个数据项(如果存在), 从存储缓冲区中刷出到内存的时间点. 这两种时间点分别对应了调用刷出和返回刷出动作. 令Σcal, Σret, Σfcal和Σfret分别为函数调用动作、函数返回动作、调用刷出动作和返回刷出动作的集合. 一个扩展历史是一个字母表(Σcal∪Σret∪Σfcal∪Σfret)*上的有穷序列, 它用来表示并发数据结构的行为, 记录了数据结构和客户程序之间的交互以及数据结构和存储缓冲区之间的交互. 我们称eh为标号迁移系统A的一条扩展历史, 如果存在A的一条迹, 且其上的扩展历史为eh. 令ehistory(A)表示标号迁移系统A的扩展历史的集合, 令eh|i为扩展历史eh在进程Pi的动作集合上的投影.
给定两个扩展历史eh1和eh2, 如果对每个进程Pi(1≤i≤n), eh1|i=eh2|i都成立, 那么称这两个扩展历史是等价的. 给定两个扩展历史eh1和eh2, 如果:
● eh1和eh2是等价的;
● 存在{1, …, |eh1|}和{1, …, |eh2|}之间的一一映射π, 使得对任意的1≤i≤|eh1|, eh1(i)=eh2(π(i))都成立;
● 任取1≤i < j≤|eh1|, 如果eh1(i)∈Σret∪Σfret且eh1(j)∈Σcal∪Σfcal, 则π(i) < π(j)成立.
我们称eh1是TSO-to-TSO可线性化到扩展历史eh2的.
给定扩展历史的集合S1和S2, 如果对每条扩展历史eh1∈S1, 都存在着扩展历史eh2∈S2, 使得eh1是TSO-to-TSO可线性化到eh2的, 则称S2是TSO-to-TSO线性化了S1的. 给定并发数据结构L1和L2, 如果ehistory(
一条k-扩展历史是一条包含不超过k个动作的扩展历史. 给定数字k和标号迁移系统A, 令k-ehistory(A)表示标号迁移系统A上的k-扩展历史集合. 我们提出了k-限界TSO-to-TSO可线性化这个概念, 它只考虑k-扩展历史, 是TSO-to-TSO可线性化的一个限界且可判定(在后面章节证明)的子类. 它的定义如下:
定义1(k-限界TSO-to-TSO可线性化). 给定并发数据结构L和L′, 如果对每条k-扩展历史eh∈k-ehistory(
TSO-to-SC可线性化[12]是可线性化在TSO内存模型下的另一个变种, 它也满足抽象定理. 它关联了一个运行在TSO内存模型下的并发数据结构以及一个运行在SC内存模型上的数据结构的抽象实现. 一条历史是一个函数调用动作和函数返回动作的序列. TSO-to-SC可线性化使用历史来表示并发数据结构的行为. 我们称h为标号迁移系统A的一条历史, 如果存在A的一条迹, 且其上的历史为h. 令history(A)为标号迁移系统A的历史的集合.
给定历史h1和h2, 如果:
● h1和h2是等价的;
● 存在{1, …, |h1|}和{1, …, |h2|}之间的一一映射π, 使得对任意的1≤i≤|h1|, h1(i)=h2(π(i))都成立;
● 取1≤i < j≤|eh1|, 如果eh1(i)∈Σret且eh1(j)∈Σcal, 则π(i) < π(j)成立.
那么称h1是TSO-to-SC可线性化到h2的.
给定并发数据结构L1和L2, 如果对每一条历史h1∈history(
一条k-历史是一条包含不超过k个动作的历史. 给定数字k和标号迁移系统A, 令k-history(A)表示标号迁移系统A上的k-历史集合. 文献[18]提出了k-限界TSO-to-SC可线性化, 它只考虑k-历史, 是TSO-to-SC的一个限界且可判定[18]的子类. 它的定义如下:
定义2(k-限界TSO-to-SC可线性化)[18]. 给定并发数据结构L和L′, 如果对每条k-历史h∈k-ehistory(
TSO可线性化[13]是可线性化在TSO内存模型下的另一个变种. 和TSO-to-TSO可线性化以及TSO-to-SC可线性化不同的是, TSO可线性化并不满足抽象定理. 给定函数返回动作return(i1, m1, a1)和函数调用动作call(i2, m2, a2), 如果i1=i2且m1=m2, 则称这两个动作配对. 一条顺序历史是一条以函数调用开始, 并且除了最后一个动作之外, 每个函数调用之后紧接着配对的函数返回动作, 而每个函数返回动作之后紧接着函数调用动作的历史. TSO可线性化将一个运行在TSO内存模型下的并发数据结构和其顺序规约关联起来, 后者是顺序历史的集合.
本质上, TSO可线性化使用函数调用动作和调用刷出动作来表示并发数据结构的行为. 给定一个扩展历史eh及函数返回动作eh(j1)=return(i, m, a)和返回刷出动作eh(j2)=flushReturn(i), 如果存在自然数g, 使得在eh|i上, eh(j1)是第g个函数返回动作, 而eh(j2)是第g个返回刷出动作, 则称这两个动作配对. 给定扩展历史eh, 令Trans(eh)是通过先删除函数返回和调用刷出动作, 再将返回刷出动作转化为配对的函数返回动作所得到的历史. Trans(eh)将一条扩展历史转化为一条“对应的历史”. TSO可线性化使用通过Trans转化扩展历史所得到的历史集合来表示并发数据结构的行为.
给定历史h, 令complete(h)为h的包含配对的函数调用动作和函数返回动作的最大子序列. 一条历史中的一个操作e包含一对函数调用动作inv(e)以及之后配对的函数返回动作res(e). 一个顺序规约是一个对前缀封闭的顺序历史的集合. 给定扩展历史eh和顺序规约Spec, 如果存在一条顺序历史s∈Spec, 并且Trans(eh)可以通过添加零个或若干个函数返回操作被扩展为一条历史h′, 使得:
● complete(h′)和s是等价的;
● 对Trans(eh)上的每一对操作e1和e2, 如果在Trans(eh)中, res(e1)先于inv(e2)发生, 则在s中, res(e1)也先于inv(e2)发生.
称eh是TSO可线性化到Spec的.
给定并发数据结构L和顺序规约Spec, 如果对任意扩展历史eh∈ehistory(
我们提出了k-限界TSO可线性化这个概念, 它只考察限界的扩展历史, 是TSO可线性化的一个限界且可判定(在后面章节证明)的子类. 它的定义如下:
定义3(k-限界TSO可线性化). 给定并发数据结构L和顺序规约Spec, 如果对每条满足Trans(eh)≤k的扩展历史eh∈ehistory(
需要注意的是, 尽管k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化只考虑限界数目的函数调用、函数返回、调用刷出和返回刷出动作, 但在每一对函数调用和函数返回之间可能发生的动作数目是不限的, 进而使用的存储缓冲区大小是不限的. 因此, 对这3个TSO内存模型下可线性化的限界版本的验证需要对无穷状态标号迁移系统(操作语义)进行验证, 是非平凡的.
4 TSO内存模型下可线性化的3个限界版本的验证思路本节给出验证TSO内存模型下可线性化的3个限界版本(k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化)的验证思路. 我们将验证两个并发数据结构符合k-限界TSO-to- TSO可线性化和k-限界TSO-to-SC可线性化问题, 以及验证并发数据结构k-限界TSO可线性化到正则的顺序规约这3个问题归约到验证两个k-扩展历史集合符合TSO-to-TSO可线性化问题, 从而以一种统一的方法来完成对TSO内存模型下可线性化的3个限界版本的验证. 注意: 在考虑k-限界TSO可线性化的可判定性问题时, 假定顺序规约是正则的.
● 统一的验证方法
我们通过如下两个步骤完成针对并发数据结构以及正则顺序规约之间的k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化的验证.
(1) 在第1步, 构造出两个限界长度(限界的长度为k或k+n)扩展历史集合S1和S2. 由于函数名集合有穷、进程数目有穷且数据域有穷, 所有可能的k-扩展历史集合和k+n-扩展历史集合(不管是否属于S1和S2)都是有穷集合, 因此S1和S2也是有穷集合;
(2) 在第2步, 依据定义1以如下方式检测S2是否TSO-to-TSO线性化了S1: 对每一对扩展历史eh∈S1和eh′∈S2, 显然它们之间的一一映射的数目是有穷的, 通过枚举这些映射并检测是否保留了特定动作之间的序, 我们可以判定eh是否TSO-to-TSO可线性化到eh′. 通过对S1中的每条扩展历史, 寻找S2中是否有与其满足TSO-to-TSO可线性化关系的扩展历史, 我们可以判定S2是否k-限界TSO-to-TSO线性化了S1. 由于S1和S2都是有穷集合, 因此第2步的计算一定终止.
当分别验证并发数据结构以及正则顺序规约之间的k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化时, 需要叙述此时集合S1和S2的定义, 以及如何构造这两个集合. 我们还需要确保对这两个集合的构造过程一定终止.
● 验证k-限界TSO-to-TSO可线性化
在验证两个并发数据结构是否符合k-限界TSO-to-TSO可线性化时, 集合S1和S2分别是k-ehistory(
(1) 首先, 枚举出所有可能的k-扩展历史的集合(不管是否对应并发数据结构的执行). 由于进程数目、函数名集合和数据域都是有穷的, 所以所有可能的k-扩展历史集合是有穷的, 并且可以通过枚举来构造;
(2) 然后, 对每一条k-扩展历史, 使用某种方法检测其是否属于
集合S2可以通过类似的方法处理并发数据结构L′来构造.
判定一条特定的扩展历史是否属于并发数据结构的实际执行是较为复杂的, 我们将在第5节−第7节详细叙述判定方法.
● 判定k-限界TSO-to-SC可线性化
由定义2可以推导出, n进程下并发数据结构L′是k-限界TSO-to-SC线性化了并发数据结构L的, 当且仅当k-history(
由于每个调用刷出或返回刷出动作都来自于一个之前发生的函数调用或函数返回动作, 如果扩展历史eh在函数调用动作和函数返回动作上的投影的长度为k, 则原扩展历史最长有2k. 因此, 可以通过如下方法计算出S1: 先依照之前的方法计算出
● 判定k-限界TSO可线性化
给定标号迁移系统A, 令k-transHistory(A)为这样的历史h的集合: 存在扩展历史eh∈ehistory(A), 使得|Trans(eh)|≤k, 且h=Trans(eh). 由定义3可知: n进程下并发数据结构L是k-限界TSO可线性化到正则的顺序规约Spec的, 当且仅当对k-transHistory(
给定历史集合S, 令k-history(S)表示S中k-历史的集合. 不难证明: n进程下并发数据结构L是k-限界TSO可线性化到顺序规约Spec的, 当且仅当posSet(k-transHistory(
每个返回刷出动作来自于之前的一个函数返回动作, 进而关联到之前的一个函数调用动作; 每个返回刷出动作发生前, 对应的调用刷出动作一定已经发生; 一个调用刷出动作来自于之前的一个函数调用动作. 因此, 如果扩展历史eh在函数调用动作和返回刷出动作上的投影的长度为k, 则原扩展历史最长有3k. 可以通过如下方法计算出k-transHistory(
因为k-transHistory(
根据第4节中的思路, 我们需要解决判定TSO内存模型下并发数据结构是否有着一条特定的扩展历史的问题. 为此, 在后面章节中参考了文献[8]的思路, 将操作语义和易失通道机器关联起来. 文献[8]中的方法未涉及函数调用、函数返回、调用刷出和返回刷出动作. 处理函数调用和函数返回动作的难点在于,
本节提出了如何修改原有的客户程序和并发数据结构, 提出了新的内存模型及其操作语义, 它使用“刷出”动作和cas动作来对应扩展历史中的函数调用、函数返回、调用刷出和返回刷出动作. 我们修改后的内存模型和操作语义与
令zf是一个新的内存单元. 我们希望将
在新的操作语义中, 我们修改了客户程序和并发数据结构, 添加了对zf的写动作. 确切地说, 给定一个并发数据结构L=(XL, ML, DL, QL, →L), 新的操作语义是一个标号迁移系统
● 修改后的最一般客户Mode(MGC)是一个五元组({zf}, M, DL∪{call, ret}, {inclt, inlib1, inlib2}, →).
我们扩展了原有的最一般客户, 在函数返回动作发生后加入了一个对zf的写动作, 写入一个函数返回标记. 迁移关系→被定义为→={(inclt, call(m, a), inlib1), (inlib1, return(m, a), inlib2), (inlib2, write(zf, ret), inclt)|m∈M, a∈DL};
● 修改后的并发数据结构Mod(L)是一个五元组(XL∪{zf}, M, DL∪{call, ret}, QL∪Q′,
修改后的并发数据结构在每个函数调用动作之后, 先执行一个对zf的写动作, 写入函数调用标记. 确切地说, 迁移关系
迁移关系→f是通过对迁移关系→tt进行如下修改得到的.
● 在进行函数调用和函数返回迁移时, 只修改当前进程的控制状态, 而不将函数调用标记或函数返回标记放入进程的存储缓冲区. 以函数调用迁移为例,
$ \frac{{p(i) = i{n_{clt}}}}{{(p, d, u){{\xrightarrow{{call(i, m, a)}}}_t}(p[i:(i{s_{(m, a)}}, i{n_{lib1}})], d, u)}}, {\text{ }}\frac{{p(i) = (f{s_{(m, a)}}, i{n_{lib1}})}}{{(p, d, u){{\xrightarrow{{return(i, m, a)}}}_t}(p[i:i{n_{lib2}}], d, u)}}; $ |
● 修改
$ \frac{{u(i) = l \cdot ({z_f}, call)}}{{(p, d, u){{\xrightarrow{{flushCall(i)}}}_f}(p, d[{z_f}:call], u[i:l])}}, {\text{ }}\frac{{u(i) = l \cdot ({z_f}, ret)}}{{(p, d, u){{\xrightarrow{{flush{Re} turn(i)}}}_f}(p, d[{z_f}:ret], u[i:l])}}; $ |
● 当存储缓冲区中其他内存单元的写刷出时, 发动刷出迁移:
$ \frac{{u(i) = l \cdot (x, a), x \ne {z_f}}}{{(p, d, u){{\xrightarrow{{flush(i, x, a)}}}_f}(p, d[x:a], u[i:l])}}. $ |
下面的引理说明了操作语义
引理1. ehistory(
在原本的TSO内存模型下, 使用读、写或cas动作对应函数调用和函数返回动作有着固有的困难, 这个困难来自于一个进程无法“及时观察”到其他进程做了函数调用或函数返回动作. 对此的解释如下: 由于一个进程P的函数调用和函数返回动作不影响存储缓冲区和内存, 因此其他进程是观测不到进程P的函数调用或函数返回动作的. 如果通过在进程P的函数调用和函数返回动作之后加入对某个内存单元x的写动作来产生其他进程可见的动作来完成这个目标, 则这个写动作需要在进程P的存储缓冲区中的其他数据项都被刷出后才能被刷出. 假定我们希望复现这样的执行, 某个进程P在执行函数调用之前, 其存储缓冲区已经有一个某内存单元y的写, 这个进程在之后的执行中从来没有刷出这个对y的写, 而且正因为这些y的写一直没刷出, 另一个进程P′的函数才能两次返回特定值(假定这两次函数返回之间执行了cas指令). 当我们尝试复现历史时, 会发现复现的历史“不真实”, 因为在复现的历史中进程P对x的写一定在进程P′的第1个函数返回之后才可能刷出到内存, 这导致在复现的历史中进程P的这个函数调用的位置在进程P′的第1个函数返回之后.如果通过在进程P的函数调用和函数返回动作之后加入cas动作来完成这个目标, 由于cas动作会清空存储缓冲区, 所以同样无法复现上面提到的执行. 为了解决这个问题, 文献[18]修改了内存模型本身. 在修改后的内存模型中, 我们将函数调用和函数返回动作与特定的cas动作“绑定”在一起. 确切地说, 引入了一个“观察者”进程和一个新的内存单元zw, 观察者进程不断地用cas动作修改zw, 修改的值是非确定猜测的; 内存模型要求在观察者进程每次对zw的cas动作后, 下一个动作必须是对应的函数调用或函数返回动作.只有观察者进程修改zw的cas动作必须被对应的进程的函数调用或函数返回动作响应, 而其他内存单元的刷出和cas动作不会让其他进程进行响应. 本节叙述如何通过这个思想来修改第5.1节构造的操作语义.
令markedVal(M, DL, n)={call(i, m, a}, return(i, m, a)|1≤i≤n, a∈DL, m∈M}为zw做cas动作使用的值, 为了对应函数调用和函数返回动作, 这些值本身就记录了函数调用或函数返回. 给定并发数据结构L=(XL, ML, DL, QL, →L), 我们修改并发系统如下.
● 进程P1到Pn仍然运行修改后的最一般客户;
● 观察者进程Pn+1运行如下客户程序: Cmarked=({zw}, M, markedVal(M, DL, n), {qwit}, →wit), 这里, 迁移关系→wit定义为{qwit, cas_suc(zw, _, a), qwit|a∈markedVal(M, DL, n)}. 在观察者进程中, 我们只考虑成功的cas动作, 可以认为另有一个陷阱状态, 当cas动作失败时, 观察者进程进入陷阱状态并终止执行.
令
● 进程Pn+1, 即观察者进程, 对应的迁移规则如下:
$ \frac{{p(n + 1) = {q_{wit}}, {q_{wit}}{{\xrightarrow{{cas\_suc({z_w}, a, b)}}}_{wit}}{q_{wit}}, a, b \in markedVal(\mathcal{M}, {\mathcal{D}_\mathcal{L}}, n), d({z_w}) = a, u(n + 1) = \varepsilon }}{{(p, d, u, \bot ){{\xrightarrow{{cas(n + 1, {z_w}, a, b)}}}_b}(p, d[{z_w}:b], u, b)}}. $ |
这里, →wit是观察者进程的客户程序Cmarked的迁移关系.
注意, 只有这种迁移会将当前格局的第4个元组从⊥修改为markedVal(M, DL, n)中的值;
● 除函数调用、函数返回动作之外, 进程P1到Pn其他的迁移规则不变, 且这些迁移只能在当前格局的第4个元组为⊥时发生;
● 一个函数调用迁移call(i, m, a)只有当格局的第4个元组的值为call(i, m, a)时才能发生, 并且这个函数调用迁移将会把这个元组的值置为⊥. 函数调用动作的迁移规则如下:
$ \frac{{p(i) = i{n_{clt}}}}{{(p, d, u, call(i, m, a)){{\xrightarrow{{call(i, m, a)}}}_b}(p[i:(i{s_{(m, a)}}, i{n_{lib1}})], d, u, \bot )}}. $ |
函数返回动作的迁移规则类似.
通过对当前格局的第4个元组的要求, 我们将观察者进程的对zw的cas动作和进程P1到Pn的函数调用和函数返回动作绑定在了一起. 下面引理说明
引理2. ehistory(
本节引入(易失)通道机器的定义. 之后, 我们先用一个例子直观说明如何使用通道机器模拟
一个经典的通道机器包含了有穷的控制状态和一个容量无界的通道, 通道机器的一步迁移在修改控制状态的同时, 也可以向通道中放入数据或者从通道中取出数据. 一个易失通道是一个在任意时刻都可能有任意数量的数据从通道中丢失的通道, 且这个数据丢失过程不会对通道机器有任何通知. 一个易失通道机器是一个使用易失通道的通道机器. 文献[8]在如下几个方面扩展了(易失)通道机器的定义.
● 每一个迁移中加入了正则卫士表达式, 判断某个通道的内容是否属于一个正则语言;
● 在每一个迁移发生之前, 允许将某个通道的元素进行替换;
● 引入了强符号的概念. 强符号是通道内容的一部分, 它们在迁移过程中是不会丢失的, 并且一个通道内的强符号数目有着上界.
在文献[18]中, 我们稍微扩展了文献[8]中(易失)通道机器的定义, 允许使用多种强符号, 并对每个通道里的每种强符号的数目分别限界. 本文使用文献[18]中的(易失)通道机器定义.
一个正则卫士表达式形如c∈L, 这里, c是一个通道名, L是一个正则集合. 给定一个序列l, 如果u∈L, 则称u‘c∈L成立. 为了表示的简便, 我们把正则卫士表达式c∈Σ*aΣ*简写为a∈c, 把正则卫士表达式c∈ε简写为c=ε, 把正则卫士表达式c∈Σ*简写为c: Σ. 一个通道集合C上的正则卫士表达式为每个通道c∈C关联一个正则卫士表达式. 我们使用Guard(C)表示通道集合C上的正则卫士表达式的集合. 关系‘也被扩展到Guard(C)上.给定函数u为每个通道分配内容以及g∈Guard(C), 如果对每个通道c∈C, u(c)‘g(c)都成立, 则称u‘g成立.
通道c上有3种操作: nop操作不修改通道, c[σ]!a操作先依据替换σ进行元素替换再向通道中插入数据a, c?a操作从通道中取出数据a. 这里, σ是一个元素替换, 当σ把每个元素替换为自己时, 将此时的c[σ]!a操作简写为c!a. 我们为每一种通道操作定义一个序列之间的关系如下: 任取有穷序列u和u′, 如果u=u′, 则
一个通道机器M被定义为一个五元组M=(Q, CH, ΣCH, Λ, Δ), 其中, Q是一个有穷的状态集合, CH是一个有穷的通道名集合, ΣCH是通道内容的有穷字母表, Λ是有穷的迁移标号集合, Δ⊆Q×(Λ∪{ε})×Guard(CH)×Op(CH)×Q是有穷的迁移关系集合. 为了阅读的方便, 我们把(q, l, g, op, q′)∈Δ写为
给定有穷序列l和l′, 如果可以通过删除l′的零个或若干个元素得到l, 则称l是l′的子串. 令S=〈SET1, …, SETm〉为一个向量, 其中每个SETi都是一个有穷集合, 代表一种强符号, 且m是一个正整数. 令K=〈k1, …, km〉为一个向量, 其中每个元素都是一个自然数, 代表对应种类的强符号在一个通道中的数目限制. 下面定义带有强符号约束(S, K)的易失通道机器, 称为(S, K)-易失通道机器. 给定u, u′: CH→
给定通道机器M的状态q和q′, 如果将M视为(S, K)-易失通道机器, 我们称其在操作语义上从格局(q, cinit)到格局(q′, cinit)的迹的集合为
给定两个采用不同通道的通道机器M=(Q, CH, ΣCH, Λ, Δ)和M′=(Q′, CH′, ΣCH, Λ, Δ′), 即CH∩CH′=∅, M和M′的乘积被定义为另一个通道机器M⊗M′=(Q×Q′, CH∪CH′, ΣCH, Λ, Δ″). Δ″中的一步非ε迁移会同时完成M和M′中使用同样标号的两个迁移, 此时, 新的卫士表达式是这两个卫士表达式的交, 新的通道操作正好包含这两个迁移的通道操作; Δ″中的一步ε迁移完成M或M′中的一步ε迁移. 文献[8]中给出了如下引理, 说明了两个易失通道机器乘积的LT集合等于两个易失通道机器各自的LT集合的交:
引理3. 给定
这里, q=(q1, q2)且
本节通过一个例子说明如何使用通道机器模拟并发系统对应一条特定的扩展历史的执行. 给定并发系统
通道机器
图 1给出了一个
图 1中的每条执行按照时间顺序从左到右画出动作, 依据属于不同的进程从上到下画动作. 为了强调每一对函数调用和函数返回动作, 我们在每个函数调用和函数返回动作下面画一条竖线, 且用横线连接一对函数调用和函数返回动作对应的竖线. 为了强调每一对调用刷出和返回刷出动作, 在每个调用刷出和返回刷出动作下面画一个圆圈, 且用横线连接一对调用刷出和返回刷出对应的圆圈. 当函数调用、函数返回、调用刷出和返回刷出动作是进程Pi的动作时, 对应的横线画为实线; 当函数调用、函数返回、调用刷出和返回刷出动作来自猜测其他进程的动作时, 画为虚线.
图 1中的术语解释如下: 为了节省空间, 我们用w(x)1表示将x的值写为1的动作, 用r(x)1表示从x读到1的动作, 用f(x)1表示从存储缓冲区中刷出一项且其将x写为1的动作, 用cas(y)1表示使用cas成功地将y的值修改为1的动作. 通道机器
我们分别称图 1(a)~图 1(d)中的执行为t, t1~t3. 可以看到, 各个进程模拟的执行(即t1~t3)和原执行t有着相同的对内存单元修改动作的序列. 这里的内存单元修改指刷出和cas动作, 这里不对两者进行区分. 然而, t1和t有一点很大的不同在于: t中有多个存储缓冲区, 而t1只有一个通道. 为了保证相同的内存修改动作的序列, t1中猜测其他进程动作的写时, 可能会让一些写“提前”发生. 例如: 在t中, P1中的w(x)1先发生但P2中的cas(y)1先修改内存; 而在t1中, 只能先让猜测的对y的写先发生, 这样对y的修改才能先发生.
6.3 通道机器本节简述通道机器
给定一条长度为k的扩展历史eh=α1·…·αk, 可以构造一个识别eh的确定型有穷自动机Aeh=(Qs, Σs, →s, Fs,
给定并发数据结构L=(XL, ML, DL, QL, →L), 令修改后的并发数据结构Mod(L)为五元组(XL∪{zf}, M, DL∪{call, ret}, QL∪Q′,
●
● 通道字母表
● Λ是迁移标号的集合, 包含写、cas、刷出、函数调用、函数返回、调用刷出、返回刷出和ε迁移. τ动作和读动作对应的迁移的标号都是ε.
● 并发数据结构对zf的写: 令
$ ((i{s_{(m, a)}}, i{n_{lib1}}), {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_i}:\varSigma , {c_i}!\beta }}_{\varDelta _i^{eh}}}(({q_t}, i{n_{lib1}}), {d_c}, {d'_g}, {q_s}, \bot ). $ |
这里,
● 客户程序对zf的写:
$ (i{n_{lib2}}, {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_i}:\varSigma , {c_i}!\beta }}_{\varDelta _i^{eh}}}(i{n_{clt}}, {d_c}, {d'_g}, {q_s}, \bot ). $ |
这里,
●
$ (q, {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_1}:\varSigma , {c_1}!\beta }}_{\varDelta _i^{eh}}}(q, {d_c}, {d'_g}, {q_s}, \bot ). $ |
这里,
● 刷出一个对zf赋值为call的写, 引发一个调用刷出迁移: 令1≤j≤n, 如果
$ (q, {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_i}:\varSigma , {c_i}?(j, {z_f}, d)}}_{\varDelta _i^{eh}}}(q, d, {d_g}, {q'_s}, \bot ). $ |
这里, d(zf)=call, 迁移标号op=flushCall(j);
●
$ (q, {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_i}:\varSigma , {c_i}!\beta }}_{\varDelta _i^{eh}}}(q, {d_c}, {d'_g}, {q_s}, \bot ). $ |
这里,
● 刷出通道中缓存的对zw的一个写: 以这个写将zw赋值为一个函数调用为例:
$ (q, {d_c}, {d_g}, {q_s}, \bot ){\xrightarrow{{op, {c_i}:\varSigma , {c_i}?(n + 1, {z_w}, d)}}_{\varDelta _i^{eh}}}(q, d, {d_g}, {q_s}, call(j, m, c)). $ |
这里, d(zw)=call(j, m, c), 且迁移标号op=flush(n+1, zw, d). 这个迁移修改了
● 进程Pi做函数调用: 令
$ (i{n_{clt}}, {d_c}, {d_g}, {q_s}, call(i, m, a)){\xrightarrow{{call(i, m, a), {c_i}:\varSigma , nop}}_{\varDelta _i^{eh}}}((i{s_{(m, a)}}, i{n_{lib1}}), {d_c}, {d_g}, {q'_s}, \bot ). $ |
这里, nop不改变通道ci的内容. 这个迁移将
● 猜测其他进程的函数调用: 令
$ (q, {d_c}, {d_g}, {q_s}, call(j, m, a)){\xrightarrow{{call(j, m, a), {c_i}:\varSigma , nop}}_{\varDelta _i^{eh}}}(q, {d_c}, {d_g}, {q'_s}, \bot ). $ |
这个迁移将
如果把
引理4. ehistory(
引理4的简要证明如下: 引理的“当”方向由引理1和引理2显然可以得到. 对“仅当”方向, 根据引理1和引理2, ehistory(
令
下面的引理说明: 给定k-扩展历史eh和一对格局, 以这对格局为起点和终点的eh对应的标记见证的存在问题(根据引理4, 即eh的存在问题)可以被归约到判断
引理5. 给定一条k-扩展历史eh.
令
当把
引理6. 给定一条k-扩展历史eh.
根据引理3,
定理7. 给定并发数据结构L和L′、顺序规约Spec以及正整数n, n进程下L′是否k-限界TSO-to-TSO线性化了L是可判定的, n进程下L′是否k-限界TSO-to-SC线性化了L是可判定的, n进程下L是否k-限界TSO可线性化到Spec是可判定的.
我们使用简单通道机器来称呼只使用迁移标号ε、且迁移规则中不使用正则卫士表达式和替换的通道机器. 简单通道机器的可达问题[20]是指: 给定简单通道机器M、格局s1和s2(一个格局包含控制状态和通道内容), 判断作为易失通道机器的M是否有一条从s1和s2的有穷执行. 文献[20]证明了简单通道机器的可达问题的复杂度在递归函数的Fast-Growing层级[19]
为了证明的方便, 我们使用单通道简单通道机器, 为此需要证明简单通道机器的可达问题与单通道的简单通道机器的可达问题可以相互归约.
● 一方面, 后者是前者的一个子问题;
● 另一方面, 我们通过如下方式将前者归约为后者: 给定简单通道机器M, 可以构造一个单通道简单通道机器M′. M′的通道内容就是使用分隔符分隔开的M的各个通道的内容. M的一步迁移中对通道内容的修改被M′上若干步迁移所模拟, 这些迁移读取整个通道的内容, 并分别修改“M的每个通道”的内容(根据分隔符确定是哪个通道). 因此, 单通道简单通道机器的可达问题与简单通道机器的可达问题有着相同的复杂度. 通过类似的思想, 可以根据易失通道机器M构造单通道简单通道机器M′. 为了M的一步迁移中对通道内容的修改被M′上若干步迁移所模拟, 这些迁移读取整个通道的内容, 使用正则卫士表达式进行判断, 进行可能的元素替换, 并分别修改“M的每个通道”的内容. 因此可以证明, 易失通道机器的控制状态可达问题和单通道的简单通道机器的可达问题可以互相归约.
由于TSO内存模型下可线性化的3种限界版本的验证问题被归约为易失通道机器的控制状态可达问题, 因此可以看到, TSO内存模型下可线性化的3种限界版本的验证问题可以被归约到单通道的简单通道机器的可达问题. 注意: 由于易失通道机器
采用类似于文献[17]的思想, 我们构造了并发数据结构
下面叙述如何通过将单通道简单通道机器的可达问题, 归约到判定
● k-限界TSO-to-TSO可线性化的复杂度
令并发数据结构Lpend包含M1和M2两个函数, 且这两个函数从不返回. 一种实现方法为M1和M2内只包含一条while(true); 语句. 当
可以证明:
● k-限界TSO-to-SC可线性化的复杂度
当
可以证明:
● k-限界TSO可线性化的复杂度
在并发数据结构
可以证明:
因此, 我们将TSO内存模型下可线性化的3种限界版本的验证问题归约到单通道的简单通道机器的可达问题, 又将后者归约到了前者. 根据以上讨论, 得到了如下定理, 它说明TSO内存模型下可线性化的3种限界版本的复杂度都在递归函数的Fast-Growing层级
定理8. 在n进程下, 检测k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化的复杂度都在递归函数的Fast-Growing层级
据我们所知, TSO-to-TSO可线性化、TSO-to-SC可线性化和TSO可线性化是文献中针对TSO内存模型提出的全部可线性化变种. 本文研究了k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化的可判定性. TSO内存模型下可线性化的3种限界版本都是非平凡的, 因为写动作的数目不限界, 因此使用的存储缓冲区大小不限界, 进而其操作语义是无穷状态迁移系统. 我们证明了TSO内存模型下可线性化的这3种限界版本都是可判定的. 我们的工作解决了TSO内存模型下限界可线性化的可判定性问题.
本文将并发数据结构以及顺序规约之间的k-限界TSO-to-TSO可线性化、k-限界TSO-to-SC可线性化和k-限界TSO可线性化的验证归约到k-扩展历史集合之间的TSO-to-TSO可线性化的验证, 从而以一种统一的方式验证TSO内存模型下可线性化的3个限界版本. 验证的重点在于判定TSO内存模型下并发数据结构是否有一条特定的扩展历史. 我们证明了这个问题是可判定的, 证明的方法是将其归约到已知可判定的易失通道机器的控制状态可达问题. 归约的难点在于: 已有的关联TSO内存模型下并发系统和易失通道机器的工作关注的问题是基于状态定义的, 而并发数据结构的正确性定义是基于函数调用、函数返回、调用刷出和返回刷出动作的, 这两个问题的粒度不同. 更加复杂的是, 函数调用和函数返回动作以一种复杂的方式同时影响控制状态和存储缓冲区. 我们的工作通过修改内存模型、并发数据结构、客户程序和操作语义, 使得内存单元zf和zw的动作序列对应了函数调用、函数返回、调用刷出和返回刷出的序列. 然后, 将修改的操作语义上是否存在特定的扩展历史这个问题归约到了易失通道机器
进而证明了在TSO内存模型下判定可线性化的这3个限界版本的复杂度都在递归函数的Fast-Growing层级
[1] |
Herlihy MP, Wing JM. Linearizability: A correctness condition for concurrent objects. ACM Trans. on Programming Languages and Systems, 1990, 12(3): 463-492.
[doi:10.1145/78969.78972] |
[2] |
Lamport L. How to make a multiprocessor computer that correctly executes multiprocess programs. IEEE Trans. on Computers, 1979, 28(9): 690-691.
|
[3] |
Sewell P, Sarkar S, Owens S, et al. x86-TSO: A rigorous and usable programmer's model for x86 multiprocessors. Communications of the ACM, 2010, 53(7): 89-97.
[doi:10.1145/1785414.1785443] |
[4] |
Sarkar S, Sewell P, Alglave J, et al. Understanding POWER multiprocessors. In: Proc. of the 32nd ACM SIGPLAN Conf. on Programming Language Design and Implementation (PLDI 2011). New York: ACM, 2011. 175−186.
|
[5] |
Pulte C, Flur S, Deacon W, et al. Simplifying ARM concurrency: Multicopy-atomic axiomatic and operational models for ARMv8. Proceedings of the ACM on Programming Languages (PACMPL), 2018, 2(POPL): Article 19.
|
[6] |
Nienhuis K, Memarian K, Sewell P. An operational semantics for C/C++11 concurrency. In: Proc. of the ACM SIGPLAN Int'l Conf. on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA 2016). 2016. 111−128.
|
[7] |
Manson J, Pugh W, Adve SV. The Java memory model. In: Proc. of the 32nd ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages (POPL 2005). New York: ACM, 2005. 378−391.
|
[8] |
Atig MF, Bouajjani A, Burckhardt S, et al. On the verification problem for weak memory models. In: Proc. of the 37th Annual ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages (POPL 2010). New York: ACM, 2010. 7−18.
|
[9] |
Owens S, Sarkar S, Sewell P. A better x86 memory model: x86-tso. In: Proc. of the TPHOLs. 2009. 391−407.
|
[10] |
Bouajjani A, Emmi M, Enea C, et al. Verifying concurrent programs against sequential specifications. In: Proc. of the 22nd European Conf. on Programming Languages and Systems (ESOP 2013). Berlin, Heidelberg: Springer-Verlag, 2013. 290−309.
|
[11] |
Burckhardt S, Gotsman A, Musuvathi M, et al. Concurrent library correctness on the TSO memory model. In: Seidl H, ed. Proc. of the 21st European Symp. on Programming (ESOP 2012). Tallinn: Springer, 2012. 87−107.
|
[12] |
Gotsman A, Musuvathi M, Yang HS. Show no weakness: Sequentially consistent specifications of TSO libraries. In: Proc. of the 26th Int'l Symp. on Distributed Computing (DISC 2012). 2012. 31−45.
|
[13] |
Derrick J, Smith G, Dongol B. Verifying linearizability on tso architectures. In: Albert E, Sekerinski E, eds. Proc. of the Integrated Formal Methods. Cham: Springer, 2014. 341−356.
|
[14] |
Batty M, Dodds M, Gotsman A. Library abstraction for C/C++ concurrency. In: Giacobazzi R, Cousot R, eds. Proc. of the 40th Annual ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages (POPL 2013). Rome: ACM, 2013. 235−248.
|
[15] |
Doherty S, Dongol B, Wehrheim H, et al. Making linearizability compositional for partially ordered executions. In: Proc. of the 14th Int'l Conf. on Integrated Formal Methods (IFM 2018). 2018. 110−129.
|
[16] |
Alur R, McMillan K, Peled D. Model-checking of correctness conditions for concurrent objects. In: Proc. of the 11th Annual IEEE Symp. on Logic in Computer Science (LICS'96). Washington: IEEE Computer Society, 1996. 219−228.
|
[17] |
Wang C, Lv Y, Wu P. TSO-to-TSO linearizability is undecidable. Acta Informatica, 2018, 55(8): 649-668.
[doi:10.1007/s00236-017-0305-6] |
[18] |
Wang C, Lv Y, Wu P. Bounded TSO-to-SC linearizability is decidable. In: Freivalds RM, Engels G, Catania B, eds. Proc. of the SOFSEM 2016. Berlin, Heidelberg: Springer, 2016. 404−417.
|
[19] |
Wainer SS. A classification of the ordinal recursive functions. Archive for Mathematical Logic, 1970, 13(3-4): 136-153.
[doi:10.1007/BF01973619] |
[20] |
Chambart P, Schnoebelen P. The ordinal recursive complexity of lossy channel systems. In: Proc. of the 23rd Annual IEEE Symp. on Logic in Computer Science (LICS 2008). Pittsburgh: IEEE Computer Society, 2008. 205−216.
|
[21] |
Liang HJ, Feng XY, Fu M. A rely-guarantee-based simulation for verifying concurrent program transformations. In: Field J, Hicks M, eds. Proc. of the 39th ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages (POPL 2012). Philadelphia: ACM, 2012. 455−468.
|
[22] |
Liang HJ, Feng XY. Modular verification of linearizability with non-fixed linearization points. In: Boehm HJ, Flanagan C, eds. Proc. of the ACM SIGPLAN Conf. on Programming Language Design and Implementation (PLDI 2013). Seattle: ACM, 2013. 459−470.
|
[23] |
Bouajjani A, Emmi M, Enea C, Hamza J. On reducing linearizability to state reachability. In: Halldorsson MM, Iwama K, Kobayashi N, Speckmann B, eds. Proc. of the 42nd Int'l Colloquium on Automata, Languages, and Programming (ICALP 2015). Kyoto: Springer, 2015. 95−107.
|
[24] |
Bouajjani A, Enea C, Wang C. Checking linearizability of concurrent priority queues. In: Meyer R, Nestmann U, eds. Proc. of the 28th Int'l Conf. on Concurrency Theory (CONCUR 2017). Berlin: Schloss Dagstuhl-Leibniz-Zentrum fur Informatik, 2017. 16: 1−16: 16.
|
[25] |
Liu Y, Chen W, Liu YA, et al. Verifying linearizability via optimized refinement checking. IEEE Trans. on Software Engineering, 2013, 39(7): 1018-1039.
[doi:10.1109/TSE.2012.82] |
[26] |
Derrick J, Smith G, Groves L, et al. A proof method for linearizability on TSO architectures. In: Proc. of the Provably Correct Systems. 2017. 61−91.
|
[27] |
Travkin O, Wehrheim H. Handling TSO in mechanized linearizability proofs. In: Yahav E, ed. Proc. of the Hardware and Software: Verification and Testing. Cham: Springer, 2014. 132−147.
|
[28] |
Atig MF. What is decidable under the TSO memory model?. ACM SIGLOG News, 2020, 7(4): 4-19.
[doi:10.1145/3458593.3458595] |
[29] |
Wang C, Lv Y, Wu P, et al. Bounded linearizability on tso memory model is decidable. Technical Report, ISCAS-SKLCS-21-01, State Key Laboratory of Computer Science, Institute of Software, Chinese Academy of Sciences, 2021. http://lcs.ios.ac.cn/~lvyi/files/ISCAS-SKLCS-21-01.pdf
|