Art of Multiprocessor Programming 答案 ch9

来源:互联网 发布:unity3d 雷电特效 编辑:程序博客网 时间:2024/04/30 12:03

100.   将add()方法的 if(cur.key == key) return false去掉,改为找到 pred.key <= key && curr.key > key为插入的位置。remove()方法和contains()方法都是找到任意一个key == key时结束。

101. 所有的锁都按照升序获得,不会有循环,所以无死锁。

102. 当线程拥有pred.lock和curr.lock的时候,与这2个节点无关的操作并行操作,结果与这个add()方法无关,可以在串行化的历史中任意安排这2个操作的顺序;同时与这2个节点相关的操作,包括在pred后插入节点,删除pred和删除curr都将在获得2个锁的时候串行执行。这是add方法的可线性化点。

103. 没有对获得锁的循环。

104. 只要有pred和curr的关系不断变化就可以。比如说当前节点  head--> ... --> A --> B--> ... --> tail ; Threada想要remove B, Threadb不断在AB之间插入节点:

Ta.pred = A, Ta.curr = B; ==> Tb.pred = A, Tb.curr = B; Tb.pred.lock(), Tb.curr.lock(); ==> Tb inserts A1 ==> A-->A1-->B 

==> Ta.pred.lock(), Ta.curr.lock() ==> Ta.validate ==> Ta.pred.next (A.next) != Ta.curr (B) ==> Ta tries remove again.

==> Tb inserts A2 ==> Ta failed ==> ...

或者重复的插入和删除:

Ta.pred = A, Ta.curr = B ==> Tb.pred = A, Tb.curr = B; Tb.pred.lock(), Tb.curr.lock() ==> Tb inserts A1 ==> A-->A1-->B

==> Ta.pred.lock(), Ta.curr.lock() ==> Ta.validate ==> Ta.pred.next != Ta.curr ==> Ta tries remove again.

==> Ta.pred = A1, Ta.curr = B; Tb.pred = A, Tb.curr = A1; Tb.pred.lock, Tb.curr.lock; ==> Tb.remove(curr) ==> A-->B

==> Ta.pred.lock, Ta.curr.lock ==> Ta.validate ==> Ta.pred not reachable ==> Ta tries remove again

==> Repeat

105.

  public boolean contains(T item) {    Node last = null, pred = null, curr = null;    int key = item.hashCode();    head.lock();    try {      pred = head;      curr = pred.next;      curr.lock();      try {        while (curr.key < key) {          pred.unlock();          pred = curr;          curr = curr.next;          curr.lock();        }        return (curr.key == key);      } finally {        curr.unlock();      }    } finally {      pred.unlock();    }  

106. 如果只有add交换顺序,remove不交换,会死锁。如果remove也交换,因为validation总是在获得2个锁之后,则算法仍然正确。

107. 如果pred可达,则它肯定能达到tail。

否则,可以用归纳法证明如果predA != null, 而且predA不可达,则predA所在的list一定能够达到一个可达的并能够达到Tail的节点(包括tail)。

当pred最开始从list删除的时候,因为有curr.lock(此时curr = predA),所以被删除的predA肯定有predA.next可达。

假设predA不可达,根据假设它所在的list一定能够达到一个可达的而且能够到达tail的节点(R);因为不可能对不可达的节点进行操作,所以增加节点并不会改变这个性质。如果删除节点R, 根据上面的证明,R一定有R-->R.next-->...-->tail ==〉predA --> ... --> R -->R.next --> ... --> tail

108. 常规的锁法之所以需要2个锁,是因为要确定pred->curr的关系,即确保pred->next在add/remove操作过程中不会改变。因为add操作只涉及到pred->next和一个将成为pred->next的新节点,所以add之需要锁pred。具体来说,考虑下面的case:

  1. ThreadA.add && ThreadB.add。ThreadA.pred.lock ==> ThreadB waiting for pred.lock ==> ThreadA.pred.next = new, new->next = ThreadA.curr ==> ThreadB.pred.lock() ==> ThreadB.valiation ==> Thread.B.pred.next != ThreadB.curr。
  2. ThreadA.add && ThreadB.remove(pred)。ThreadA.pred.lock ==> ThreadB.pred.pred.lock(), ThreadB.pred waiting for lock ==> ThreadA.pred.next = new, Thread.new.next = curr ==> ThreadB.pred.lock() ==> ThreadB.pred.pred.next = new
  3. ThreadA.add && ThreadB.remove(pred)。ThreadB.pred.pred.lock(), ThreadB.pred.lock() ==> ThreadA.pred waiting for lock ==> ThreadB.pred.pred.next = ThreadB.cur; ==> ThreadA.pred.lock() ==> ThreadA.validate ==> pred is not reachable
  4. ThreadA.add && ThreadB.remove(curr)。ThreadA.pred.lock ==> ThreadB.pred waiting for lock ==> ThreadA.pred.next = new, ThreadA.pred.next.next = ThreadA.cur; ==> ThreadB.pred.lock() ==> ThreadB.validate ==> ThreadB.pred.next != ThreadB.cur
  5. ThreadA.add && ThreadB.remove(curr)。ThreadB.pred.lock(), ThreadB.cur.lock() ==> ThreadA.pred waiting for lock ==> ThreadB.pred.next = ThreadB.cur.next ==> ThreadA.pred.lock() ==> ThreadA.validate ==> ThreadA.pred.next != ThreadA.curr
109. contains()是一个有明确定义的方法,它的起作用的时间点在方法返回的时候,所以如果contains的返回在remove之后则contains必须返回false。所以题目中的alternative不是可线性化的,因为它不能覆盖这样的case:
ThreadA.pred.lock, ThreadA.cur.lock ==> ThreadA.pred.next = new, Thread.new.next = Thread.cur; ==> A-->A1-->B ==> ThreadB.contains(A1) ==> ThreadB.validate = true, ThreadB.cur = A1 ==> ThreadA.remove(A1) ==> A-->B ==> ThreadB.cur.key == A1 ==> ThreadB.contains(A1) return true.

110. 不能。
head-->A-->B-->C-->tail ==> ThreadA.remove(B) ==> ThreadA.B.next = null ==> ThreadB.contains(C) ==> ThreadB.B.next == null ==> C is not reachable.
lock-less也不能。

111. 如图9.20 (a)所示。

112. 不能。因为add()会破坏这种关系:
ThreadA.pred = A, ThreadA.cur = B ==> A-->B ==> ThreadB.add() ==> A-->A1-->B ==> ThreadA inserts ==> A-->A2-->B

113. 不能。因为remove会对pred和curr做modify,但只锁一个node,比如说ThreadA.pred == ThreadB.cur && 只锁pred,则ThreadB可以在没有锁保护的情况下modifyThreadA.pred,即Thread.pred的锁并没有起作用。具体说来其结果如图9.22 (b)所示。

114. 好处:分担add/remove的复杂性,尽快清除节点。
缺点:承担add/remove的复杂性,contains性能抖动大。

115. 不需要。因为pred.marked==false, 且pred.key < key;所以要找的curr肯定在pred节点的后面,而且可达。

116. 不能。因为这2种算法是利用排好序的链表找目标节点可能在的位置。

117. 如下所示。 其中find()返回除了pred/curr之外的可以重用的Node。
因为          snip = pred.next.compareAndSet(curr, succ, false, false); 所以只有一个线程能够物理删除可重用的节点,之后这个节点就不可达了,而且处于被标记的状态。所以只有一个线程能够得到这个可重用的节点,包括运行contains()的节点。因此在add()方法中对这个节点的赋值不需要同步机制,所以只要这个节点到达了add()就可以像new Node那样使用了。
package p117;/* * LockFreeList.java * * Created on January 4, 2006, 2:41 PM * * From "Multiprocessor Synchronization and Concurrent Data Structures", * by Maurice Herlihy and Nir Shavit. * Copyright 2006 Elsevier Inc. All rights reserved. */import java.util.concurrent.atomic.AtomicMarkableReference;/** * Lock-free List based on M. Michael's algorithm. * @param T Item type. * @author Maurice Herlihy */public class LockFreeList<T> {  /**   * First list node   */  Node head;  /**   * Constructor   */  public LockFreeList() {    this.head  = new Node(Integer.MIN_VALUE);    Node tail = new Node(Integer.MAX_VALUE);    while (!head.next.compareAndSet(null, tail, false, false));  }  /**   * Add an element.   * @param item element to add   * @return true iff element was not there already   *///fulltopic: Adjusted to reuse marked node    public boolean add(T item) {    int key = item.hashCode();Node node = null;    while (true) {      // find predecessor and curren entries      Window window = find(head, key);      Node pred = window.pred, curr = window.curr;      // is the key present?      if (curr.key == key) {        return false;      } else {        // splice in new node    if(node == null)    {    if(window.reuseNode != null)    {    node = window.reuseNode;    node.item = item;    }else    {    node = new Node(item);    }    }node.next = new AtomicMarkableReference<LockFreeList<T>.Node>(curr, false);        if (pred.next.compareAndSet(curr, node, false, false)) {          return true;        }      }    }  }  /**   * Remove an element.   * @param item element to remove   * @return true iff element was present   */  public boolean remove(T item) {    int key = item.hashCode();    boolean snip;    while (true) {      // find predecessor and curren entries      Window window = find(head, key);      Node pred = window.pred, curr = window.curr;      // is the key present?      if (curr.key != key) {        return false;      } else {        // snip out matching node        Node succ = curr.next.getReference();        snip = curr.next.attemptMark(succ, true);        if (!snip)          continue;        pred.next.compareAndSet(curr, succ, false, false);        return true;      }    }  }  /**   * Test whether element is present   * @param item element to test   * @return true iff element is present   */  public boolean contains(T item) {    int key = item.hashCode();    // find predecessor and curren entries    Window window = find(head, key);    Node curr = window.curr;    return (curr.key == key);  }  /**   * list node   */  private class Node {    /**     * actual item     */    T item;    /**     * item's hash code     */    int key;    /**     * next node in list     */    AtomicMarkableReference<Node> next;    /**     * Constructor for usual node     * @param item element in list     */    Node(T item) {      // usual constructor      this.item = item;      this.key = item.hashCode();      this.next = new AtomicMarkableReference<Node>(null, false);    }    /**     * Constructor for sentinel node     * @param key should be min or max int value     */    Node(int key) { // sentinel constructor      this.item = null;      this.key = key;      this.next = new AtomicMarkableReference<Node>(null, false);    }  }    /**   * Pair of adjacent list entries.   */  class Window {    /**     * Earlier node.     */    public Node pred;    /**     * Later node.     */    public Node curr;        public Node reuseNode;    /**     * Constructor.     */    Window(Node pred, Node curr) {      this.pred = pred; this.curr = curr;    }        //fulltopic: Set userNdoe    Window(Node pred, Node curr, Node reuseNode)    {    this(pred, curr);    this.reuseNode = reuseNode;    }  }    /**   * If element is present, returns node and predecessor. If absent, returns   * node with least larger key.   * @param head start of list   * @param key key to search for   * @return If element is present, returns node and predecessor. If absent, returns   * node with least larger key.   */  public Window find(Node head, int key) {    Node pred = null, curr = null, succ = null, reuseNode = null;    boolean[] marked = {false}; // is curr marked?    boolean snip;    retry: while (true) {      pred = head;      curr = pred.next.getReference();      while (true) {        succ = curr.next.get(marked);         while (marked[0]) {           // replace curr if marked          snip = pred.next.compareAndSet(curr, succ, false, false);          if (!snip) continue retry;          //fulltopic: get use node          if(curr.key == key)          {          reuseNode = curr;          }          curr = pred.next.getReference();          succ = curr.next.get(marked);        }        if (curr.key >= key)          return new Window(pred, curr, reuseNode);        pred = curr;        curr = succ;      }    }  }}

118. 因为contains() --> find() 会忽略所有marked的节点。如果线性化的顺序是remove-->add-->contains,contains().find()会发现curr.key == key而不是停止在marked removed node with key == key。

0 0