多处理器编程的艺术 第二版 chapter 5 第五章 笔记

前言

本章目的:构造一些原语,希望我们能利用这些原语来解决一些同步问题。

consensus:  一个同步原语的共识数是它能解决共识问题的最大线程数

上面的无法通过下面的synchronization primitives实现出来。

 注意,这里的逻辑是,我们制造出来一些同步工具/同步原语,然后利用共识数来对这些同步工具进行测试,看看这些同步工具厉不厉害。我们制造出同步工具不是专门为共识数测试设计的,但共识数就像一个“能力试炼场”,能测出它们的强弱。测试结果好的(共识数高),就可以放心拿去其他并发场景用,比如构建复杂数据结构或协议。

5.1 Consensus numbers

每个线程calls the decide() method with its input v at most once. 最多一次

•consistent: all threads decide the same value,
•valid: the common decision value is some thread’s input.

被选中的要先完成。比如v被选中了,那么执行v的线程先完成了decide()方法调用。

 每一个线程只执行一次,线程数量有限,lock-free告诉我们总有一些线程在前进,那最终所有的线程也会执行完毕,所以lock-free也是wait-free。

在wait-free下实现consensus:consensus protocol

我们想了解某一类对象是否足够强大以解决共识问题。如何让这个概念更精确?
1. 如果我们认为这类对象是由系统的较低层(可能是操作系统或硬件)支持的,那么我们关心的是这类对象的属性,而不是对象的数量。(如果系统能提供一个这类对象,通常也能提供更多。)
2. 其次,假设任何现代系统都能提供大量读写内存用于记录管理是合理的。这两个观察结果引出了以下定义。

 Definition 5.1.1. 我们说,class C 解决了 n线程公式问题 如果:使用了任意数量的C的对象+任意数量的寄存器,能够实现n线程consensus protocol

 

Definition 5.1.2. The consensus number of a class C is the largest n for which that class solves n-thread consensus. If no largest n exists, we say the consensus number of the class is infinite.

 

Corollary 5.1.3. Suppose one can implement an object of class C from one or more objects of class D, together with some number of atomic registers. If class C solves n-consensus, then so does class D.

 

5.1.1 States and valence

initial:move之前的ps

 

The decision value of any final state is the value decided by all threads in that state.
注意上面这句话,无论在之前经过多少次move,在最后状态下,所有的线程应该有一个decision value,这个值应该是一样的。

 

bivalent:后代最后结果有0有1 

univalent:只有一个

(书上的那个图,不需要管0和1哪来的,直接往上面推就行了)

其实有点像几个人往棋盘(共享变量)下棋,在某个阶段,游戏可能已经结束,大家达成共识谁赢谁输。每次只能有一个人下棋(满足sequential)

 

Lemma 5.1.5. Every n-thread consensus protocol has a bivalent initial state.

跟两个线程一样证,比较显然

 

critical:几个人下棋,总有一天要有一个结果,那么下出了得出结果的前一步棋的那个状态就是critical state,后续下的棋对结果没什么影响,已经收敛。

 

5.2 Atomic registers

atomic registers 做不到吗

 

Theorem 5.2.1. Atomic registers have consensus number 1.


运行系统直到critical state

3种情况:1. 一个线程要从一个寄存器读; 2. 两个线程往两个reg写; 3.两个线程往一个reg写

1. A线程读,对于B来说,在A读之前和A读之后,他根本不知道有什么区别(state完全一致,看不到A的本地值),又要怎么得出两个不同的decision value呢?不成

2. A,B往两个reg写,无论什么顺序,same protocol state(而我们假定,在相同的state,应该会有一样的树,结果也是一样的),所以就不是critical state,也不成

3. A写完B直接覆盖,与B自己写,两种state对于B来说完全一样,也是不可能得出两个不同的decision value。

 

Corollary 5.2.2. It is impossible to construct a wait-free implementation of any object with consensus number greater than 1 using atomic registers.

无法使用原子寄存器(atomic registers)构建一个无等待(wait-free)实现的任何共识数(consensus number)大于 1 的对象。
必须要更多的原子操作,普通的读写不够用
 

5.3 Consensus protocols

协议让线程来提交自己的值,然后再自定义一个decide的实现

5.4 FIFO queues

原文比较完整,此处补充
Theorem 5.4.3. FIFO queues have consensus number 2

的第二种情况的图

以及

C都是分不清的,state完全一样

 

5.5 Multiple assignment objects

Theorem 5.5.1. There is no wait-free implementation of an (m,n)-assignment object by atomic registers for any n > m > 1.

Proof. It is enough to show that we can solve 2-consensus given two threads and
a (2,3)-assignment object. (Exercise 5.26 asks you to justify this claim.)


这里的意思应该是,(2,3)-assignment object都有共识数2了,那前面已经说了atomic register大于一的全都造不出来

Corollary 5.2.2. It is impossible to construct a wait-free implementation of any ob-
ject with consensus number greater than 1 using atomic registers.

比较严谨的是,要证明(2,3)在所有(m,n)中是最弱的。这是显然的(应该),因为所有的(m,n)都可以分出来2,3的功能,比如3,4,天然就需要提供3格位置,2次的数据填充。

所以得证。注意这里用的是lock-base的操作,属于是作弊了,不是wait-free。

 

对Theorem 5.5.2的证明比较有意思,5.5.2的意思是,(n,n(n+1)/2)-assignment有能力让n个线程取得共识,具体是这样做的,将n(n+1)/2分成两部分,第一部分是0~n-1,对应n个线程,这些寄存器是私有的,只有线程自己写。剩下就是n(n+1)/2-n=n(n-1)/2个线程,给他们编上号,比如n=3,n(n-1)/2就是3个,编号r10, r20, r21。0号assign,就会给r0, r10, r20三个寄存器放入自己投票的值。

对于case2,d写的东西全都在,一看就是最后写的,淘汰掉。a跟b对应的0,1,我们看r10寄存器,发现写的a,所以a也在b之前写入的,所以就是1号最先写入。

这里的严格证明就不补了。

 

5.6 Read–modify–write operations

RMW操作一般是cpu内置操作。

一组函数被称为 非平凡的(nontrivial),如果它至少包含一个不是 恒等函数(identity function) 的函数。这里的意思就是,我们的rmw()会修改本来在寄存器的值(恒等函数就是不修改,比如说get())。
Theorem 5.6.1中,我们假设r.rmw()就是给v+1,第一个来到的肯定会发现v还是那个v,就直接获胜了。
Corollary 5.6.2 还是上面那个,既然RMW已经共识数2了,那普通的原子寄存器就造不出来了。

5.7 Common2 RMW operations

普通2号RMW的问题,在于虽然赢家知道自己赢了,输家知道自己输了,但是输家不知道谁赢,自然也找不到共识数了。

比如说,getAndIncrement(),a先到,v+1,b再到,v+2,c一看数字v+2傻眼了,是a还是b最早到的?(对应fA(fB(v)) = fB(fA(v)))

再比如,getAndSet(),c一看里面存的b,也是傻眼,虽然知道了自己已经来晚了,但是,有可能b先到了,a还在自己后面呢(b是胜者)?也有可能a先到,b再到,把a的数值给覆盖了(a是胜者)?(对应fi(fj(v)) = fi(v) or fj(fi(v)) = fj(v).)

5.8 The compareAndSet operation

并发大王,CMPXCHG。

5.8.1:compareAndSet()+get() = infinite consensus number

线程A如果返回了true,那么在线性化时间顺序上,A就是第一个拿到结果的,如果false,就去获取胜者的结果。

Corollary 5.8.2,不需要get(),我的理解是,不妨保存下来总共的线程数,比如10个线程,我们有int t = 10, 然后循环for i = 0~9 {if(compareAndSet(i, i)){return proposed[i];}}无非就是挨个检查到底是谁的线程id。

当然只是麻烦一点而已了。

 

posted @ 2025-04-13 09:27  映空城  阅读(9)  评论(0)    收藏  举报