CSC 2/458, 20 Mar.ff 2019 A4 due 5 April. Semester project proposals due Saturday. Monday and Wednesday next week will be devoted to 3-5 min. presentations by all students in the class. See the assignment. Recall linearizability, nonblocking progress variants. With locks, the linearization point of an operation can be arbitrarily declared to be anywhere between lock acquire & release. To be nonblocking, an operation has to have a single instruction at which it is said to "happen". We have to ensure that (a) prior to that point other threads behave as if the operation had not started ("harmless prep"), and (b) after that point other threads behave as if the operation had completed ("mere cleanup"). Leader election (consensus) with CAS is wait-free. Treiber stack is lock-free but not wait-free. SPSC queue from a couple weeks ago is wait-free, but ONLY FOR A SINGLE PRODUCER & CONSUMER. There's a natural obstruction-free deque (that isn't lock-free). DSTM (early object-based STM system) is also obstruction-free but not lock-free. Book presents the following structures. Memory management is a cross-cutting theme. single-word counter * No memory management Treiber stack counted pointers, incremented in pop only * Never dereference a pointer in a dynamically allocated node; Can deallocate freed data immediately so long as the OS never removes it from our address space. M&S queue dummy node have to read value _before_ making node dummy notion of consistent snapshots operations at 2 ends are independent unless queue is empty enqueue linearizes on CAS to last node's /next/ ptr tail update is cleanup * _Do_ dereference pointers in dynamically allocated nodes. This can be made safe with counted pointers if we also use a type-preserving allocator, so "once a pointer, always a pointer" H&M lists marked pointers Harris presented w/ GC or w/ counted pts & type-preserving allocator * Michael added hazard pointers. This requires eager removal of marked nodes. Harris's original alg. had removed them lazily. non-extensible hash tables straightforward application of H&M lists S&S extensible hash tables order numbers dummy nodes lazy initialization indexing into the header arrays Michael lock-free deque interesting in the complexity of multi-step operations prob. not worth class time HLM obstruction-free deque described this briefly a couple weeks ago Matt Graichen, Joe Izraelevitz, and I published an unbounded extension in 2016 ---------------------------------------- Dual data structures. formalization: insert, remove_request, remove_followup latter can be _successful_ or _unsuccessful_ rules (0) All three ops must be total and nonblocking. (1) Each unsuccessful remove_followup must refrain from remote memory access. (2) When a matching insert occurs, a waiting thread must wake up "right away". More formally, if insert operation I in thread t matches successful remove_followup operation S in thread u, then no other operation (in particular, neither an unsuccessful remove_followup in u nor a successful remove_followup in any other thread) is permitted to linearize between I and S. in practice, remove_request.remove_followup* can be packaged as just remove dual queue is fairly straightforward if queue is empty on attempted remove, or if its full of requests, insert a request instead if queue is full of requests on insert, satisfy and remove instead. Satisfaction is linearization point; anybody can remove. Request owner is responsible for reclamation. dual stack is slightly trickier Can't satisfy in place, because somebody may push something on top. Then queue may contain Stacks & queues used as "fair" and "unfair" "queues" in j.u.c. executor framework. Improved throughput of unfair mode by 3x; fair mode by 14x. Strange, though, to require same FIFO/LIFO discipline for data and requests. Izraelevitz & Scott TOPC'17 shows how to combine any data container with almost any request container (has to support peek and remove_conditional). Challenge is to verify that one subcontainer is empty and insert into the other, atomically. Accomplished using "placeholders". These can be invalid, aborted, valid, or satisfied. State changed by CAS. To insert data: - insert invalid placeholder into data container - check for emptiness of antidata container, aborting any unvalidated placeholders removed - if a validated placeholder is found, mix and return - if antidata container is empty, attempt to validate placeholder - if aborted, restart - this can be reciprocal, making the construction obstruction-free, not lock-free Manage placeholders using hazard pointers. Choose linearization points dynamically in retrospect (tricky proof). This doesn't yet quite satisfy dual liveness: can't remove antidata placeholder and then mix. Instead - peek at item that would be removed - publicly post intent - to satisfy with a given value - as the _active request_ - satisfy (mix) in-place \ - remove_conditional \ - clear public request / These last 3 steps can be helped This "active request" mechanism satisfies the rules and ensures dual liveness, but has a significant negative impact on throughput. ---------------------------------------- Book makes passing reference to Laden-Mozes & Shavit queue S&T doubly-linked lists skip lists work stealing deques elimination esp. pretty for stack Hendler et al. flat combining reduce synchronization OH and cache misses by having lock holder execute all pending ops, from all threads. Not appropriate for all data structures: need sequential application of k ops to be faster than k times the cost of one. Wins on synch. OH, combining, and cache locality. LCRQ [Morrison & Afek] use FAI to get slot in which to enqueue or dequeue, most of the time add new queues if the current one fills (or seems to -- "tantrum" semantics) recover when consumers run ahead of producers typos in the the original in fig. 3b: line 45: s/h/idx (x2) bug in Fig. 5b: after line 145, add v := dequeue(crq) if (v != EMPTY) return v CRQ ideal enqueue FAI on tail; mod to find slot CAS to insert CRQ ideal dequeue FAI on head; mod to find slot CAS to remove CRQ enqueue exceptions (1e) slot already has data in it, due to wrap-around, or (2e) dequeuer appears (as evidenced by too-high index or cleared safe bit) to have gotten here first in either case, skip slot and let dequeuer clean up CRQ dequeue exceptions (1d) enqueue has not yet run, or (2d) skipped this slot (when enqueue arrives, it will be in case 2e) in either case, if slot is empty, increase slot index by R; OW clear the 'safe' bit also if queue is (or appears) full, throw tantrum and extend if slots are unsafe or enqueue fails to make progress, throw tantrum and extend if queue is empty and dequeuer is way ahead, use CAS to advance tail a lot, all at once if head is less than current enqueuer's index and slot is empty but unsafe, override unsafe mark to salvage slot Izraelevitz & Scott TOPC'17 also introduced two dual versions of the LCRQ. Multi-parity variant (MPDQ) is faster when closing the preemption window; single-parity variant (SPDQ) is faster when not. Outperform S&S M&S-based DQ by 4x-6x.