Art of Multiprocessor Programming 答案 ch7

来源:互联网 发布:淘宝的集市店 编辑:程序博客网 时间:2024/04/30 12:43

85. 因为线程自己的节点还被后续节点和tail引用。

被后续节点引用的case: 2个线程A,B

A<--B

B.lock() --> B.pred=A --> B.pred.locked=true --> A.unlock() --> A.locked = false 

==> A wants lock again : A.lock() --> A.locked=true --> A.pred = B --> A.pred.locked()=true 

==> B wakes up: B.pred.locked =true --> A.pred.locked=true

==> deaklock

被tail引用:

tail=A --> A.lock() --> A.unlock() --> A.lock() --> A.locked=true --> A.pred=tail.getAndSet(A)

==> A.pred=A --> deadlock


86. 第二种比较好。

第一种实现在最好的情况下,比如说同步程度非常高,则每个线程占用总线实现cache miss --> read --> update --> write;然后每个线程重新load得到已经更新到过路障的值,总共有n次回写,n次cache miss。一般的情况下,每一次更新都可以类比于tta锁的unlock,带来n个cache miss,即n^2次cache miss。

第二种情况下,最坏的情况下每个更新只引起一个回写和一个cache miss;加上最后一个线程引起n个cache miss;也是n次回写,2n次cache miss。


87. 互斥

如果已经有一个线程拥有fastpath锁,与它竞争的线程在竞争fp锁的时候或者(oldStamp & FASTPATH) != 0 或者 tail.compareAndSet会失败。继续慢速通道的时候会有pred需要等,或者有fastPathWait()需要等fp锁释放。

如果已经有一个现成拥有慢速锁,与他竞争的线程在试图得到fp锁的时候会发现 qnode != null; 继续走慢速通道时根据普通compositeLock互斥。

饥饿

CompositeLock只有在抢到了队列中的节点并加入tail链之后才会开始排队等锁,在抢队列节点和加入tail的时候是没有公平性的。所以在acquireQNode和spliceQNode时候的CAS都可能引起饥饿。

同时,fastPathUnlock()中的CAS也可能引起所有其他线程的饥饿,因为不断有线程timeout,不断有现成开始抢锁,导致tail不断被更新,使这个cas退不出来。


88. 如果这个算法不正确,错误的case应该是这个关键节点同时使localqueue和globalqueue的preNode,如果localqueue一直不知道自己是master,localqueue就不能被加入globalqueue中,最后的关系如下所示:

node<-- ... <-- current node(A) <-- master of another cluster(B) <-- other nodes of another cluster 

                        /|\____ (master) of local queue(C) <-- other nodes of my cluster

则当current node释放锁的时候,可能会有2个master同时进入critical section,破坏互斥。

但是这是不可能发生的,因为C将自己加到localqueue以后,要执行myPred.waitForGrantOrClusterMaster()。这个方法使C等到A放锁或者发现自己是local master。但是A在放锁之前一定会先执行 localTail.setTailWhenSpliced(true),即B在发现A.isSuccessorMustWait() == false之前一定有A.isTailWhenSpliced() == true;所以在进入critical region之前会先履行master的职责。


89. 会出现每次每一个cluster只有一个node被加到globalqueue中的情况,即完全没有体现层次锁的优点。可以加一个timer,如果master发现localqueue上的节点太少,时间也充分就等待一段时间。


90. 有一种case可能会出问题:即 ClusterA中的NodeA1已经是localqueue(A)中的tail,加入到globalQueue后被ClusterB中的NodeB回收;同时NodeA2要加到localqueue(A)中。

如果NodeB执行NodeA1.unlock()时,不是原子的同时更改clusterId, SMW和TWS,而是先更改TWS=false。则NodeA2.waitForGrantOrClusterMaster()会发现

getClusterID() == myCluster && !isTailWhenSpliced() && !isuccessorMustWait()

==〉NodeA2会获得锁,即使当前globalqueue上还有其他等待的Node。

如果NodeA1.unlock()时先设置clusterId,除非cpu决定乱序执行,否则应该可以不用原子的修改state。因为如果NodeA1.unlock()是设置的clusterId等于原clusterId,则NodeA2从localqueue(A)中得到的myPred != NodeA1; 如果不等于原clusterId,NodeA2.waitForGrantOrClusterMaster()会发现NodeA2是master,然后从globalQueue去锁。


91.

TAS

boolean isLocked(){    return state.get();}

CLH:

boolean isLocked(){    return tail.get().locked;}

MCS:

boolean isLocked(){    QNode node = queue.get();    if(node == null)    {      return false;    }else    {      return node.locked;    }}

92. 原来的证明过程中现成必须先写某个寄存器然后判断全局状态,如果用R-M-W操作,比如CAS,可以先读然后决定是否写,所以可以避免覆盖的情况。

1 0
原创粉丝点击