1 / 77

Concurrent Programming Without Locks

Explore the motivation behind non-blocking solutions without locks for concurrent programming. Learn about obstruction freedom, lock-freedom, and wait-freedom, as well as the use of descriptors in transactional memory systems. Discover how to conditionally update multiple locations atomically by introducing a level of indirection.

pjeffrey
Download Presentation

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

  2. Motivation • Locking introduces dependencies among threads • Non-blocking solutions keep threads independent, but • they are complicated to program • or depend on unrealistic instructions (CAS2) • Need a practical and general non-blocking solution

  3. Solutions? • Only use data structures that can be implemented with CAS? • Limiting • Build MCAS in software using CAS • Build Transactional Memory in software using CAS

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

  5. 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

  6. The Basic Problem • How can we conditionally update multiple locations atomically – using “real” instructions that can only update a single location atomically? • The trick • Introduce a level of indirection • Use descriptors to access values indirectly

  7. How do we use indirection? • Memory locations involved in a transaction or MCAS have • old and new uncommitted values stored in a descriptor • a status field determines which value to use • we must be careful how status is updated! • Memory locations not involved in a transaction can hold their value directly • requires tidying up after transactions commit

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

  9. Direct or Indirect? • How do we know if the value in a location should be used directly or indirectly? • we can reserve some low order bits • interpret them on each access • but this limits the use of the approach to aligned pointers

  10. Using Descriptors in TM • Commit operation atomically updates status field • we have to do it with CAS to avoid races • Once a descriptor is made visible, only the status field changes • Why? • How? • Once a transaction’s outcome is decided, the status value doesn’t change • Retries use a new descriptor … why? • Descriptors are managed via garbage collection

  11. Other requirements • Descriptors must be able to own locations • one transaction must not unlink another • why? • so what should be done on a conflict, wait? • But doesn’t this introduce blocking? • not necessarily – contending threads could help the owner complete

  12. Uncontended Commits • To be obstruction free, uncontended commits must succeed • The phases: • Prepare the transaction descriptor (use CCAS for each location accessed) to atomically link locations while outcome is undecided • Decide the transaction’s outcome and update the status field (using CAS) • Update memory (using CAS) and mark the descriptor for collection

  13. Contended Commits • Contended Commits must make progress • If status is decided, but not complete • Help the other thread complete • If status is undecided, either • Abort contending transactions • needs contention management to prevent live-lock • Help contending transactions • need some way to ensure success of at least one transaction • Read-check, used in WSTM or OSTM to ensure read set is still current: • Abort at least one contender • Help, and ensure progress by ordering transactions

  14. Three STM Implementations MCAS Multiple Compare And Swap WSTM Word Software Transactional Memory OSTM Object Software Transactional Memory

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

  16. MCAS • Operates only on aligned pointers • enables use of 2 low order bits to distinguish values from descriptors • Descriptors contain • status {Success, Failure, Undecided} • N • address[ ] • expected[ ] • new_value[ ]

  17. Status: SUCCESS Address Old Value New Value 101 100 200 Status: UNDECIDED Address Old Value New Value 107 100 200 Data Access Examples descriptor value 300 descriptor

  18. The Prepare Phase • Create MCAS descriptor • Insert descriptor address in each location • don’t overwrite other concurrent attempts • don’t keep working if another thread has already helped you succeed or fail • use CAS conditional on undecided status (CCAS) • MCAS descriptor must not become visible until its fully initialized • link CCAS descriptors in each location first then swap for MCAS descriptor using CCAS

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

  20. CCAS • Create a new private CCAS descriptor • Copy CCAS parameter values into it • Try to link it into the target location (using CAS) • On failure try to help whoever succeeded by using their CCAS descriptor • again using CAS • then retry your own

  21. 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); }

  22. Cost in terms of CAS • CCAS takes at least 2 CAS to link the MCAS descriptor into each location • 2N CAS for N locatons • But we still have not committed the MCAS • at least 1 CAS required to set MCAS status • at least N CAS required to update the memory locations with the new values from the MCAS descriptor

  23. Reading • We can’t simply read values anymore! • CCASRead must be used for reading • It must be able to read values directly and indirectly through CCAS descriptors • detect which situation it is in • function correctly in the presence of concurrent updates

  24. CCASRead • Copy address to be read to local • Test to see if it’s a value or a descriptor • If it’s a descriptor help the thread whose descriptor it is complete • requires more CAS

  25. word *CCASRead(word **a) { word *v = *a; while ( IsCCASDesc(v) ) { CCASHelp( (ccas_descriptor *)v); v = *a; } return v; } void CCASHelp(ccas_descriptor *d) { bool success = (*d->cond == UNDECIDED); CAS(d->a, d, success ? d->n : d->e); }

  26. Reading • We also need an MCASRead to read locations subject to MCAS • MCASRead used CCASRead to read the contents of the location • if its an MCAS descriptor it must find the address in the descriptor and determine whether to use the old or new values • this requires more CCAS

  27. Putting it all together • Example MCAS (3, {a,b,c}, {1,2,3}, {4,5,6})

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

  29. UNDECIDED 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

  30. UNDECIDED 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 CCAS Descr a 1 &MCAS_Descr &mcas->status

  31. UNDECIDED 3 a 1 4 b 2 5 c 3 6 MCAS(3, {a,c,b}, {1,3,2}, {4,6,5}) a b 2 c 3 CCAS Descr a 1 &MCAS_Descr &mcas->status

  32. UNDECIDED 3 a 1 4 b 2 5 c 3 6 MCAS(3, {a,c,b}, {1,3,2}, {4,6,5}) a b 2 c 3 CCAS Descr a 1 &MCAS_Descr &mcas->status

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

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

  35. 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

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

  37. 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); }

  38. 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:

  39. 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; }

  40. 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 UNDECIDED if (CCASRead(addr) == v) return v->expected[i]; else goto retry_read; } } return v; }

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

  42. 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:

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

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

  45. 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:

  46. Status: UNDECIDED Address Old Value New Value 102 100 200 104 456 789 108 200 777 Status: UNDECIDED Address Old Value New Value 104 456 123 108 999 200 Conflicts 102 104 108

  47. 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; }

  48. Status: SUCCESS Address Old Value New Value 102 100 200 104 456 789 108 200 777 Status: UNDECIDED Address Old Value New Value 104 456 123 108 999 200 Conflicts 102 104 108

  49. 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; }

  50. Status: SUCCESS Address Old Value New Value 102 100 200 104 456 789 108 200 777 Status: UNDECIDED Address Old Value New Value 104 456 123 108 999 200 Conflicts 200 789 777

More Related