1 / 60

Lock-Free Design: Concurrent Programming Without Locks

This presentation explores lock-free design in concurrent programming, including the use of descriptors and multiple compare-and-swap (MCAS) operations. It covers the motivation, solutions, algorithms, and design considerations for achieving non-blocking progress.

stotler
Download Presentation

Lock-Free Design: Concurrent Programming Without Locks

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Keir Fraser & Tim Harris Concurrent Programming Without Locks Adapted from an earlier presentation by Phil Howard

  2. Motivation • Locking precludes parallelism • Recall “A Lock-Free Multiprocessor OS Kernel” by Massalin et al • Extensive use of CAS2 (aka DCAS, DCADS) • instruction does not exist on today’s CPUs • Need a practical and general non-blocking solution

  3. Solutions? • Only use data structures that can be implemented with CAS? • Limiting • RCU • Still uses locks for writers • Still limited to CAS data structures • Software MCAS • Transactional Memory

  4. Goals • Concreteness • Linearizability • Non-blocking progress guarantee • Disjoint access parallelism • Read parallelism • Dynamicity • Practicable space costs • Composability

  5. Caveats • “It remains possible for a thread to see a mutually inconsistent view of shared memory if it performs a series of [read] calls.”

  6. Definitions • Obstruction freedom– a thread will make progress as long as it doesn’t contend with other threads access to any location • Lock-freedom – The system as a whole will make progress • Wait-freedom – Every thread makes progress Focus is on Lock-free design Whole transactions are lock-free, not just the sub-components

  7. Design considerations • Need to update multiple locations atomically – using only “real” instructions • The secret? • Indirection! • Use descriptors to access values

  8. Status Address Old Value New Value 102 100 200 105 123 123 106 456 789 100 Descriptor 101 102 103 104 105 106 107 Memory

  9. Implications of Descriptors • Commit operation atomically updates status field • All accesses are indirect • Need to distinguish between descriptor or value • Need to choose “actual”, “old”, or “new” value • Once a descriptor is made visible, only the status field changes • Once an outcome is decided, the status value doesn’t change • Retries use a new descriptor • Descriptors are managed via garbage collection

  10. Other requirements • Descriptors must be able to own locations • Uncontended commits must work • Prepare phase • Decision point • Update status value • Clean up • Status values: UNDECIDED, READ-CHECK,SUCCESSFUL, FAILED

  11. Other Requirements • Contended Commits must make progress • Decided, but not complete • Help the other thread complete • Undecided, not read-check • Abort contending transactions • Without contention management can lead to live-lock • Help contending transactions • Sort memory addresses to prevent looping • Read-check • Abort at least one contender • Prevent live-locks by totally ordering transactions

  12. Algorithms MCAS Multiple Compare And Swap WSTM Word Software Transactional Memory OSTM Object Software Transactional Memory

  13. MCAS CAS( word *address, // actual value word expected_value, word new_value); (logically) MCAS( int count, word *address[], // actual values word expected_value[], word new_value[]); (but an extra indirection is added) (pointers must indirect through the descriptor!)

  14. MCAS • Operates only on aligned pointers • Lower 2 bits used to distinguish value/descriptor • Descriptors contain • status • N • address[] • expected[] • new_value[]

  15. Status: SUCCESS Address Old Value New Value 102 100 200 Status: UNKNOWN Address Old Value New Value 105 100 200 Data Access descriptor value 300 descriptor

  16. CCAS Conditional CAS built from CAS - takes effect only if condition == undecided - used to insert descriptor references CCAS( word *address, word expected_value, word new_value, word *condition); return original value of *address

  17. Word *MCASRead(word **addr) { word *v; retry_read: v = CCASRead(addr); if ( !IsMCASDesc(v)) return v; for (int i=0; i<v->N; i++) { if (v->addr[i] == addr) { if (v->status == SUCCESS) if (CCASRead(addr) == v) return v->new[i] else goto retry_read; else // FAILED or UNKNOWN if (CCASRead(addr) == v) return v->expected[i]; else goto retry_read; } } return v; }

  18. MCAS(3, {a,b,c}, {1,2,3}, {4,5,6})‏ a 1 b 2 c 3

  19. UNKNOWN 3 a 1 4 b 2 5 c 3 6 MCAS(3, {a,c,b}, {1,3,2}, {4,6,5}) a 1 b 2 c 3

  20. SUCCESS 3 a 1 4 b 2 5 c 3 6 MCAS(3, {a,b,c}, {1,2,3}, {4,5,6}) a 4 1 1 b 2 5 2 c 3 3 6

  21. bool MCAS(int N, word **a[], word *e[], word *n[]) { mcas_descriptor *d = new mcas_descriptor(); d->N = N; d->status = UNDECIDED; for (int i=0; i<N; i++) { d->a[i] = a[i]; d->e[i] = e[i]; d->n[i] = n[i]; } address_sort(d); return mcas_help(d); }

  22. bool mcas_help(mcas_descriptor *d) { word *v, desired = FAILED; bool success; // Phase 1: acquire for (int i=0; i<d->N; i++) { while (TRUE){ v = CCAS(d->a[i], d->e[i], d, • &d->status); if (v = d->e[i] || v == d) break; if (IsMCASDesc(v) ) mcas_help( (mcas_descriptor *)v ); else goto decision_point; } } desired = SUCCESS; decision_point:

  23. mcas_help continued // PHASE 2: read – not used by MCAS decision_point: CAS(&d->status, UNDECIDED, desired); // PHASE 3: clean up success = (d->status == SUCCESS); for (int i=0; i<d->N; i++) { CAS(d->a[i], d, success ? d->n[i] : d->e[i]); } return success; }

  24. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 789 108 999 777 Claiming Ownership 102 104 CCAS Descr 108 999 108 999 &MCAS_Descr &mcas->status

  25. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 789 108 999 777 Claiming Ownership 102 104 CCAS Descr 108 999 108 999 &MCAS_Descr &mcas->status

  26. word *CCAS(word **a, word *e, word *n, word *cond) { ccas_descriptor *d = new ccas_descriptor(); word *v; (d->a, d->e, d->n, d->cond) = (a,e,n,cond); while ( (v = CAS(d->a, d->e, d)) != d->e ) { if ( IsCCASDesc(v) ) CCASHelp( (ccas_descriptor *)v); else return v; } CCASHelp(d); return v; } void CCASHelp(ccas_descriptor *d) { bool success = (*d->cond == UNDECIDED); CAS(d->a, d, success ? d->n : d->e); }

  27. word *CCASRead(word **a) { word *v = *a; while ( IsCCASDesc(v) ) { CCASHelp( (ccas_descriptor *)v); v = *a; } return v; }

  28. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 789 108 999 777 Status: UNKNOWN Address Old Value New Value 108 999 200 Conflicts 102 104 108

  29. bool mcas_help(mcas_descriptor *d) { word *v, desired = FAILED; bool success; // Phase 1: acquire for (int i=0; i<d->N; i++) { while (TRUE){ v = CCAS(d->a[i], d->e[i], d, &d->status); if (v = d->e[i] || v == d) break; if (IsMCASDesc(v) ) mcas_help( (mcas_descriptor *)v ); else goto decision_point; } } desired = SUCCESS; decision_point:

  30. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 789 108 999 777 Status: UNKNOWN Address Old Value New Value 108 999 200 Conflicts 102 104 108

  31. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 789 108 999 777 Conflicts 102 104 108 200

  32. bool mcas_help(mcas_descriptor *d) { word *v, desired = FAILED; bool success; // Phase 1: acquire for (int i=0; i<d->N; i++) { while (TRUE){ v = CCAS(d->a[i], d->e[i], d, &d->status); if (v = d->e[i] || v == d) break; if (IsMCASDesc(v) ) mcas_help( (mcas_descriptor *)v ); else goto decision_point; } } desired = SUCCESS; decision_point:

  33. Status: UNKNOWN Address Old Value New Value 102 100 200 104 456 456 108 999 999 Status: UNKNOWN Address Old Value New Value 104 456 123 108 999 200 Conflicts 102 104 108

  34. bool mcas_help(mcas_descriptor *d) { word *v, desired = FAILED; bool success; // Phase 1: acquire for (int i=0; i<d->N; i++) { while (TRUE){ v = CCAS(d->a[i], d->e[i], d, &d->status); if (v = d->e[i] || v == d) break; if (!IsMCASDesc(v) ) goto decision_point; mcas_help( (mcas_descriptor *)v ); } } desired = SUCCESS; decision_point:

  35. mcas_help continued // PHASE 2: read – not used by MCAS decision_point: CAS(&d->status, UNDECIDED, desired); // PHASE 3: clean up success = (d->status == SUCCESS); for (int i=0; i<d->N; i++) { CAS(d->a[i], d, success ? d->n[i] : d->e[i]); } return success; }

  36. CCAS “failure modes” • Someone helped us with the CCAS • call CCASHelp with our own descriptor • next time around, return MCAS descriptor • MCAS continues • Someone else beat us to CCAS • help them with their CCAS • next time around, return their MCAS descriptor • Help with their MCAS • Our MCAS likely aborts • Source value changed • return new value • MCAS aborts

  37. word *CCAS(word **a, word *e, word *n, word *cond) { ccas_descriptor *d = new ccas_descriptor(); word *v; (d->a, d->e, d->n, d->cond) = (a,e,n,cond); while ( (v = CAS(d->a, d->e, d)) != d->e ) { if ( !IsCASDesc(v) ) return v; CCASHelp( (ccas_descriptor *)v); } CCASHelp(d); return v; } void CCASHelp(ccas_descriptor *d) { bool success = (*d->cond == UNDECIDED); CAS(d->a, d, success ? d->n : d->e); }

  38. CCASHelp “failure modes” • MCAS aborted so status isn’t UNKNOWN • old value put back in place • MCAS aborted, CCASHelp doesn’t restore value • MCAS cleanup will put old value back in place • Race: status switches to SUCCESS between check and CAS • CAS will fail because CCAS descriptor already removed • CCAS return will not cause MCAS failure • Race: status switches to FAILURE between check and CAS • CAS will always fail because for MCAS to fail, someone must have read beyond us

  39. Cost • 3N + 1 CAS instructions (plus all the other code) • “it is worth noting that the three batches of N updates all act on the same locations” • “[improvements] may be useful if there are systems in which CAS operates substantially more slowly than an ordinary write.”

  40. Deep Breath

  41. WSTM • Remove requirement for space reserved in values being updated • WSTM keeps track of locations rather than caller • Provides read parallelism • Obstruction free, not lock free nor wait free

  42. Data Structures 100 Orecs Status: Undecided 200 a1: (100,15) -> (200,16)‏ version52 a2: (200,52) -> (100,53)‏ 300 400

  43. Logical contents • Orec contains a version number: • value comes direct from memory • Orec contains a descriptor reference • descriptor contains address • value comes from descriptor based on status • descriptor does not contain address • value comes direct from memory

  44. Transaction Process • Call WSTMRead/WSTMWrite to gather/change data • Builds transaction data structure, but it’s NOT visible • WSTMCommitTransaction • Get ownership – update ORecs • Read-Check – check version numbers • Decide • Clean up

  45. Data Structures 100 200 version 15 version 16 Status: UNKNOWN Status: SUCCESS 200 100 a1: (100,15) -> (200,16) version52 version 53 a2: (200,52) -> (200,52)‏ a2: (200,52) -> (100,53) 300 400

  46. Complications • Fixed number of Orecs • Hash collisions lead to false sharing

  47. Issues • Orec ownership acts like a lock, so simple scheme is not even obstruction free • Can’t help with “cleanup” because might overwrite newer data • Can’t determine value during READCHECK, so we’re forced to shoot down • force_decision() might be circular causing live lock • helping requires <complicated> stealing of transactions • Uncontended cost is N+2

  48. OSTM • Objects are represented as opaque handles • can’t use pointers directly • must rewrite data structures • Get accessible pointers via OSTMOpenForReading/OSTMOpenForWriting • Eliminates need for Orecs/aliasing

  49. Evaluation • “We use … reference-counting garbage collection” • Evaluated with one thread/CPU • “Since we know the number of threads participating in our experiments…”

  50. Uncontended Performance

More Related