AtomicReference Atomically update reference ce class • At Atomi micR cRefere rence – Java.util.concurrent.atomic package Public object get(); Public object get(); Public Public boolean boolean compareAndSet compareAndSet (T expected, T new); (T expected, T new); Apply CAS: if expected value, change to new 39 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq head tail Read value 40 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq Make first Node new sentinel CAS head tail 41 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Queue node public class public class Node { Node { public public T value; T value; public public AtomicReference AtomicReference<Node> next; <Node> next; public public Node(T value) { Node(T value) { this.value this.value=value; =value; next=new next=new AtomicReference AtomicReference<Node>(null); <Node>(null); } } } } 42 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Queue node public class Node { public class Node { public public T value; T value; public public AtomicReference AtomicReference<Node> next; <Node> next; public Node(T value) { public Node(T value) { this.value this.value=value; =value; next=new next=new AtomicReference AtomicReference<Node>(null); <Node>(null); } } } } Value stored by node 43 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Queue node public class Node { public class Node { public T value; public T value; public public AtomicReference AtomicReference<Node> next; <Node> next; public Node(T value) { public Node(T value) { this.value this.value=value; =value; next=new next=new AtomicReference AtomicReference<Node>(null); <Node>(null); } } } } Reference to next queue node 44 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Queue node public class Node { public class Node { public T value; public T value; public public AtomicReference AtomicReference<Node> next; <Node> next; public public Node(T value) { Node(T value) { this.value this.value=value; =value; next=new next=new AtomicReference AtomicReference<Node>(null); <Node>(null); } } } } New node created with null ‘next’ 45 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public public boolean boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while while ( (true true) { ) { Node last = tail.get Node last = tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if if (last == (last == tail.get tail.get()) { ()) { if if (next == (next == null null) { ) { if if ( (last.next.compareAndSet last.next.compareAndSet(null null,node ,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } } else else { { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } } } } } 46 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public public boolean boolean enq enq(T value) { (T value) { Node Node node=new Node(value); node=new Node(value); while (true) { while (true) { Node last = tail.get Node last = tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if (last == tail.get if (last == tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } Create new node } } } } 47 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public boolean public boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while (true) { while (true) { Node last = Node last = tail.get tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if (last == tail.get if (last == tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } Repeat until successful } } } } 48 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public boolean public boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while (true) { while (true) { Node last = Node last = tail.get tail.get(); (); Node next = last.next.get Node next = last.next.get(); (); if (last == if (last == tail.get tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } Read tail and its next reference } } } } 49 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public boolean public boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while (true) { while (true) { Node last = Node last = tail.get tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if (last == tail.get if (last == tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } } } If no need to fix tail, CAS last . next } } 50 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public boolean public boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while (true) { while (true) { Node last = Node last = tail.get tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if (last == tail.get if (last == tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } If successful, try to fix tail } } } } 51 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq pseudo-code public boolean public boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while (true) { while (true) { Node last = Node last = tail.get tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if (last == tail.get if (last == tail.get()) { ()) { if (next == null) { if (next == null) { if ( if (last.next.compareAndSet last.next.compareAndSet(null,node null,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } else { } else { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } } } Try to fix tail } } 52 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T public T deq deq() throws () throws EmptyException EmptyException{ { while while ( (true true) { ) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if if (first == last) { (first == last) { if if (next == (next == null null) { ) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } else else { { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } } } 53 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while (true) { while (true) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if (first == last) { if (first == last) { if (next == null) { if (next == null) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } else { } else { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } Return value or throw EmptyException } } 54 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while while ( (true true) { ) { Node first = Node first = head.get head.get(); (); Node last = tail.get Node last = tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if (first == last) { if (first == last) { if (next == null) { if (next == null) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } else { } else { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } Repeat until completed } } 55 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while (true) { while (true) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = first.next.get Node next = first.next.get(); (); if if (first == last) { (first == last) { if (next == null) { if (next == null) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } else { } else { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } If head and tail are same node… } } 56 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while (true) { while (true) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if (first == last) { if (first == last) { if if (next == (next == null null) { ) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } else { } else { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; If queue contains only sentinel, } } } } it is empty } } 57 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while (true) { while (true) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = first.next.get Node next = first.next.get(); (); if (first == last) { if (first == last) { if (next == null) { if (next == null) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } else { } else { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } Otherwise, tail should be fixed } } } } 58 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq pseudo-code public T deq public T deq() throws () throws EmptyException EmptyException{ { while (true) { while (true) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); Node next = first.next.get Node next = first.next.get(); (); if (first == last) { if (first == last) { if (next == null) { if (next == null) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } else else { { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } Try to dequeue from first node } } } } 59 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq linearization points public public boolean boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while while ( (true true) { ) { Node last = tail.get Node last = tail.get(); (); Node next = Node next = last.next.get last.next.get(); (); if if (last == (last == tail.get tail.get()) { ()) { if if (next == (next == null null) { ) { if if ( (last.next.compareAndSet last.next.compareAndSet(null null,node ,node) { ) { tail.compareAndSet tail.compareAndSet(last,node last,node); ); return; return; } } } } else else { { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } } } } } 60 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Enq linearization points public public boolean boolean enq enq(T value) { (T value) { Node node=new Node(value); Node node=new Node(value); while while ( (true true) { ) { Node last = Node last = tail.get tail.get(); (); Node next = last.next.get Node next = last.next.get(); (); if if (last == (last == tail.get tail.get()) { ()) { if if (next == (next == null null) { ) { if if ( (last.next.compareAndSet last.next.compareAndSet(null null,node ,node) { ) { Upon tail.compareAndSet tail.compareAndSet(last,node last,node); ); success return; return; } } } } else else { { tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } } } } } 61 } } Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Michael & Scott queue Deq linearization points public T public T deq deq() throws () throws EmptyException EmptyException{ { while while ( (true true) { ) { Node first = Node first = head.get head.get(); (); Node last = tail.get Node last = tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if if (first == last) { (first == last) { if if (next == (next == null null) { ) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } else else { { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } } } Danny Hendler, SPTCC summer school, 62 } } Saint-Petersburg, 2017
Michael & Scott queue Deq linearization points public T public T deq deq() throws () throws EmptyException EmptyException{ { while while ( (true true) { ) { Node first = Node first = head.get head.get(); (); Node last = tail.get Node last = tail.get(); (); Node next = Node next = first.next.get first.next.get(); (); if if (first == last) { (first == last) { if if (next == (next == null null) { ) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } else else { { T value = T value = next.value next.value; ; Upon if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) success return value; return value; } } } } } } Danny Hendler, SPTCC summer school, 63 } } Saint-Petersburg, 2017
Michael & Scott queue Deq linearization points public T public T deq deq() throws () throws EmptyException EmptyException{ { while while ( (true true) { ) { Node first = Node first = head.get head.get(); (); Node last = Node last = tail.get tail.get(); (); When Node next = Node next = first.next.get first.next.get(); (); empty if if (first == last) { (first == last) { if if (next == (next == null null) { ) { throw new throw new EmptyException EmptyException(); (); } } tail.compareAndSet tail.compareAndSet(last,next last,next); ); } } else else { { T value = T value = next.value next.value; ; if ( if (head.compareAndSet head.compareAndSet(first,next first,next)) )) return value; return value; } } } } } } Danny Hendler, SPTCC summer school, 64 } } Saint-Petersburg, 2017
Talk Outline n Preliminaries n A simple lock-free stack algorithm Linearizability o n Michael & Sco] queue algorithm n The Harris-Michael linked list algorithm n EliminaGon-based stack n Discussion & conclusions 65 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Set interface q Unordered collecGon of items q No duplicates q Methods – add(x) put x in set – remove(x) take x out of set – contains(x) tests if x in set 66 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List-based sets public interface Set<T> { public interface Set<T> { public public boolean boolean add(T x); add(T x); public public boolean boolean remove(T x); remove(T x); public public boolean boolean contains(T x); contains(T x); } } Add item to set 67 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List-based sets public interface Set<T> { public interface Set<T> { public public boolean boolean add(T x); add(T x); public public boolean boolean remove(T x); remove(T x); public public boolean boolean contains(Tt x); contains(Tt x); } } Remove item from set 68 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List-based sets public interface Set<T> { public interface Set<T> { public public boolean boolean add(T x); add(T x); public boolean public boolean remove(T x); remove(T x); public public boolean boolean contains(T x); contains(T x); } } Is item in set? 69 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List Node public class public class Node { Node { public public T item; T item; public public int int key; key; public public Node next; Node next; } } 70 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List Node public class Node { public class Node { public public T item; T item; public int key; public int key; public Node next; public Node next; } } item of interest 71 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List Node public class Node { public class Node { public T item; public T item; public public int int key; key; public Node next; public Node next; } } Usually hash code 72 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
List Node public class Node { public class Node { public T item; public T item; public int key; public int key; public public Node next; Node next; } } Reference to next node 73 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set - ∞ a b c + ∞ Sorted with Sentinel nodes (min & max possible keys) 74 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d Danny Hendler, SPTCC summer school, 75 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 76 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 77 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 78 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 79 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 80 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 81 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 82 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 83 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required q Scan list from leP to right, apply operaGon `at the right place’ q Not so simple… a b c d remove(c) remove(c) SUCCESS X SUCCESS Danny Hendler, SPTCC summer school, 84 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Use compare-and-swap (CAS)! a b c d remove(c) remove(c) Danny Hendler, SPTCC summer school, 85 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Use compare-and-swap (CAS)! a b c d CAS remove(c) remove(c) Danny Hendler, SPTCC summer school, 86 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Use compare-and-swap (CAS)! CAS a b c d CAS remove(c) remove(c) Danny Hendler, SPTCC summer school, 87 Saint-Petersburg, 2017 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d 88 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 89 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 90 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 91 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 92 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 93 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 94 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 95 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a b c d remove(c) remove(b) 96 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Why synchronization is required (2) q Apply operaGon `at the right place’ using CAS q Not so simple… a X c d remove(c) remove(b) 97 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Logical remove, then physical remove q Scan list from leP to right q Apply modificaGons using CAS q Separate removal to two steps – Logical removal: mark node to be deleted – Physical removal: change predecessor's next reference 98 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Logical remove, then physical remove a a d b c 99 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
The List-Based Set Logical remove, then physical remove a a d b c Present in list 100 Danny Hendler, SPTCC summer school, Saint-Petersburg, 2017
Recommend
More recommend