Quantitative Reasoning for Proving Lock-Freedom Jan Ho ff mann, - - PowerPoint PPT Presentation
Quantitative Reasoning for Proving Lock-Freedom Jan Ho ff mann, - - PowerPoint PPT Presentation
Quantitative Reasoning for Proving Lock-Freedom Jan Ho ff mann, Michael Marmar, and Zhong Shao Quantitative Reasoning for Proving Lock-Freedom Jan Ho ff mann, Michael Marmar, and Zhong Shao Mike is at LICS too. Quantitative Reasoning for
Jan Hoffmann, Michael Marmar, and Zhong Shao
Quantitative Reasoning for Proving Lock-Freedom
Mike is at LICS too.
Jan Hoffmann, Michael Marmar, and Zhong Shao
Quantitative Reasoning for Proving Lock-Freedom
Concurrent Data Structures
Multiprocessor OS Kernel
Threads Shared Memory
Concurrent Data Structures
Multiprocessor OS Kernel
1 77 2 8128
Threads Shared Memory
Concurrent Data Structures
Multiprocessor OS Kernel
1 77 2
Threads Shared Memory
Concurrent Data Structures
Multiprocessor OS Kernel
1 77 2 666
Threads Shared Memory
2012
Concurrent Data Structures
Multiprocessor OS Kernel
1 77 2
Threads Shared Memory
Concurrent Data Structures
Multiprocessor OS Kernel
1 77 2
Threads Shared Memory
Need synchronization to avoid race conditions.
Non-Blocking Synchronization
Non-Blocking Synchronization
- Classical Synchronization: locks ensure mutual exclusion of threads
➡ Performance issues on modern multiprocessor architectures
- Blocking (busy waiting)
- Cache-coherency (high memory contention)
Non-Blocking Synchronization
- Classical Synchronization: locks ensure mutual exclusion of threads
➡ Performance issues on modern multiprocessor architectures
- Blocking (busy waiting)
- Cache-coherency (high memory contention)
- Non-Blocking Synchronization: shared data is accessed without locks
➡ Outperforms lock-based synchronization in many scenarios
- Interference of threads possible
- Need to ensure consistency of the data structure
How to Ensure Consistency Without Locks?
How to Ensure Consistency Without Locks?
- Attempt to perform an operation
- Repeat operations after interference has been detected
How to Ensure Consistency Without Locks?
- Attempt to perform an operation
- Repeat operations after interference has been detected
Optimistic synchronization.
How to Ensure Consistency Without Locks?
- Attempt to perform an operation
- Repeat operations after interference has been detected
- Ensure that a concurrent execution is equivalent to some sequential
execution
- Desired properties: linearizability or serializability
- Different program logics exist, e.g., in [Fu et al. 2010]
- Contextual refinement, e.g., in [Liang et al. 2013]
Optimistic synchronization.
How to Ensure Consistency Without Locks?
- Attempt to perform an operation
- Repeat operations after interference has been detected
- Ensure that a concurrent execution is equivalent to some sequential
execution
- Desired properties: linearizability or serializability
- Different program logics exist, e.g., in [Fu et al. 2010]
- Contextual refinement, e.g., in [Liang et al. 2013]
Optimistic synchronization. Sequential consistency.
How to Ensure Consistency Without Locks?
- Attempt to perform an operation
- Repeat operations after interference has been detected
- Ensure that a concurrent execution is equivalent to some sequential
execution
- Desired properties: linearizability or serializability
- Different program logics exist, e.g., in [Fu et al. 2010]
- Contextual refinement, e.g., in [Liang et al. 2013]
- But: We also need additional progress guarantees.
Optimistic synchronization. Sequential consistency.
Sequential Consistency is Not Enough
Livelocks
1 77 2 8128
Threads Shared Memory
Sequential Consistency is Not Enough
Livelocks
1 77 2 8128
Threads Shared Memory
Interference Repeat
- peration
Repeat
- peration
Sequential Consistency is Not Enough
Livelocks
1 77 2 8128
Threads Shared Memory
Data structure is consistent but system is stuck (livelock).
Interference Repeat
- peration
Repeat
- peration
Progress Properties
Let be a shared-memory data structure with operations
- Assume a system with m threads that access exclusively via the
- perations
- Assume that all code outside the data structure operations
terminates
- Fix an arbitrary scheduling of the m threads in which one or more
- perations have been started
π1, ... , πk π1, ... , πk D πi D πi
Progress Properties
- A wait-free implementation guarantees that every thread can complete
any started operation of the data structure in a finite number of steps
- A lock-free implementation guarantees that some thread will complete
an operation in a finite number of steps
- An obstruction-free implementation guarantees the completion of an
- peration for any thread that eventually executes in isolation
- Wait-freedom implies lock-freedom
- Lock-freedom implies obstruction-freedom
Progress Properties
- A wait-free implementation guarantees that every thread can complete
any started operation of the data structure in a finite number of steps
- A lock-free implementation guarantees that some thread will complete
an operation in a finite number of steps
- An obstruction-free implementation guarantees the completion of an
- peration for any thread that eventually executes in isolation
- Wait-freedom implies lock-freedom
- Lock-freedom implies obstruction-freedom
No livelocks and no starvation.
Progress Properties
- A wait-free implementation guarantees that every thread can complete
any started operation of the data structure in a finite number of steps
- A lock-free implementation guarantees that some thread will complete
an operation in a finite number of steps
- An obstruction-free implementation guarantees the completion of an
- peration for any thread that eventually executes in isolation
- Wait-freedom implies lock-freedom
- Lock-freedom implies obstruction-freedom
No livelocks and no starvation. No livelocks.
Progress Properties
- A wait-free implementation guarantees that every thread can complete
any started operation of the data structure in a finite number of steps
- A lock-free implementation guarantees that some thread will complete
an operation in a finite number of steps
- An obstruction-free implementation guarantees the completion of an
- peration for any thread that eventually executes in isolation
- Wait-freedom implies lock-freedom
- Lock-freedom implies obstruction-freedom
No livelocks and no starvation. No livelocks.
Liang et al. Characterizing Progress Properties via Contextual Refinements. CONCUR’13.
Our Results
New quantitative technique to verify lock-freedom
- Uses quant. compensation schemes to pay for possible interference
- Enables local and modular reasoning
- Our paper: Formalization based on concurrent separation logic [O’Hearn
2007] and quantitative separation logic [Atkey 2010]
- Running example: Treiber’s non-blocking stack (a classic lock-free data
structure)
Our Results
New quantitative technique to verify lock-freedom
- Uses quant. compensation schemes to pay for possible interference
- Enables local and modular reasoning
- Our paper: Formalization based on concurrent separation logic [O’Hearn
2007] and quantitative separation logic [Atkey 2010]
- Running example: Treiber’s non-blocking stack (a classic lock-free data
structure) Sweet spot: strong progress guaranty and efficient, elegant implementations.
Our Results
New quantitative technique to verify lock-freedom
- Uses quant. compensation schemes to pay for possible interference
- Enables local and modular reasoning
- Our paper: Formalization based on concurrent separation logic [O’Hearn
2007] and quantitative separation logic [Atkey 2010]
- Running example: Treiber’s non-blocking stack (a classic lock-free data
structure) Sweet spot: strong progress guaranty and efficient, elegant implementations. Classically: temporal logic and whole program analysis.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; }
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; }
Stack is a linked list.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; }
Stack is a linked list. Shared pointer to the top element.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
Prepare update.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
Compare-and-swap
- peration: if the address
&S contains t then write x into &S and return true; else return false Prepare update.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
Compare-and-swap
- peration: if the address
&S contains t then write x into &S and return true; else return false CAS-guarded while-loop to detect interference. Prepare update.
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); } value_t pop() { Node *t, *x; do { t = S; if (t == NULL) { return EMPTY; } x = t->next; } while(!CAS(&S,t,x)); return t->data; }
Treiber’s Non-Blocking Stack
struct Node { value_t data; Node *next; }; Node *S; void init() { S = NULL; } void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); } value_t pop() { Node *t, *x; do { t = S; if (t == NULL) { return EMPTY; } x = t->next; } while(!CAS(&S,t,x)); return t->data; }
CAS-guarded while-loop to detect interference.
Proving Lock-Freedom: Step One
Reducing the problem to termination Let be a share-memory data structure with operations .
π1, ... , πk D
Definitions
Sn = {op1; ... ; opn | ∀i : opi ∈ {π1, ... , πk}} S = [
n∈N
Sn Pm = { n
i=1,...,m
si | ∀i : si ∈ S} P = [
m∈N
Pm
Theorem
D is lock-free ⇐ ⇒ every P ∈ P terminiates
Proving Lock-Freedom: Step One
Reducing the problem to termination Let be a share-memory data structure with operations .
π1, ... , πk D
Definitions
Sn = {op1; ... ; opn | ∀i : opi ∈ {π1, ... , πk}} S = [
n∈N
Sn Pm = { n
i=1,...,m
si | ∀i : si ∈ S} P = [
m∈N
Pm
Theorem
D is lock-free ⇐ ⇒ every P ∈ P terminiates
m threads execute finite number of
- perations
Proving Lock-Freedom: Step One
Reducing the problem to termination Let be a share-memory data structure with operations .
π1, ... , πk D
Definitions
Sn = {op1; ... ; opn | ∀i : opi ∈ {π1, ... , πk}} S = [
n∈N
Sn Pm = { n
i=1,...,m
si | ∀i : si ∈ S} P = [
m∈N
Pm
Theorem
D is lock-free ⇐ ⇒ every P ∈ P terminiates
m threads execute finite number of
- perations
Problem: Termination of one thread depends of the behavior of
- ther threads.
Proving Lock-Freedom: Step Two
Prove an upper bound on the number of loop iterations How to reason about one operation locally when the number of loop iterations depends on the other threads? Observation In a lock-free data structure, every iteration of an unsuccessful operation is caused by the successful completion of an operation by another thread.
P ∈ P πi
Idea: Threads that perform a successful operation have to pay for the additional loop iterations that they (potentially) cause in other threads.
Proving Lock-Freedom: Step Two
Prove an upper bound on the number of loop iterations How to reason about one operation locally when the number of loop iterations depends on the other threads? Observation In a lock-free data structure, every iteration of an unsuccessful operation is caused by the successful completion of an operation by another thread.
P ∈ P πi
Idea: Threads that perform a successful operation have to pay for the additional loop iterations that they (potentially) cause in other threads. Quantitative compensation scheme.
A Quantitative Compensation Scheme for Treiber’s Stack
A Quantitative Compensation Scheme for Treiber’s Stack
- Every thread has a number of tokens to pay for its loop iterations
- For Treiber’s stack, a thread needs m tokens to execute a push or pop
- After the execution the tokens disappear
- If n operations are executed then m*n is a bound on the loop iterations
A Quantitative Compensation Scheme for Treiber’s Stack
- Every thread has a number of tokens to pay for its loop iterations
- For Treiber’s stack, a thread needs m tokens to execute a push or pop
- After the execution the tokens disappear
- If n operations are executed then m*n is a bound on the loop iterations
Number of threads.
A Quantitative Compensation Scheme for Treiber’s Stack
- Every thread has a number of tokens to pay for its loop iterations
- For Treiber’s stack, a thread needs m tokens to execute a push or pop
- After the execution the tokens disappear
- If n operations are executed then m*n is a bound on the loop iterations
- Successful push/pop: 1 token is used to pay for the loop iteration
s m-1 tokens are transferred to other threads
- Unsuccessful push/pop: 1 token is used to pay for the loop iteration
s 1 token is received from a successful thread Number of threads.
A Quantitative Compensation Scheme for Treiber’s Stack
- Every thread has a number of tokens to pay for its loop iterations
- For Treiber’s stack, a thread needs m tokens to execute a push or pop
- After the execution the tokens disappear
- If n operations are executed then m*n is a bound on the loop iterations
- Successful push/pop: 1 token is used to pay for the loop iteration
s m-1 tokens are transferred to other threads
- Unsuccessful push/pop: 1 token is used to pay for the loop iteration
s 1 token is received from a successful thread Number of threads. Availability of m tokens is a loop invariant.
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1 1 1 1
Need m tokens for each push operation.
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1 1 1 1
Need m tokens for each push operation.
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1 1 1 1
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
Failed Failed Succeeded
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
Failed Failed Succeeded
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
Failed Failed Succeeded
Exits loop. Pays m-1 tokens to
- ther threads.
Iterate loop. Receive a token from successful thread. Iterate loop. Receive a token from successful thread.
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
Failed Failed Succeeded
Exits loop. Pays m-1 tokens to
- ther threads.
Iterate loop. Receive a token from successful thread. Iterate loop. Receive a token from successful thread.
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
A Quantitative Compensation Scheme for Treiber’s Stack
8128
Threads Shared Memory
77 2 1 void push(value_t v) { Node *t, *x; x = new Node(); x->data = v; do { t = S; x->next = t; } while(!CAS(&S,t,x)); }
1 1 1 1 1 1 1 1 1
Treiber’s stack is lock- free iff we have enough tokens to pay for all loop iterations.
Quantitative Reasoning in Separation Logic
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
P and Q are predicates on program states.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
P and Q are predicates on program states. Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Separating conjunction: heap can be split so that
- ne part satisfies P and
the other part satisfies R.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Separating conjunction: heap can be split so that
- ne part satisfies P and
the other part satisfies R. Frame rule for modular and local reasoning.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q]
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning. Use separating conjunction to combine it with other predicates.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning. Use separating conjunction to combine it with other predicates. There are n tokens available.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
P ^ B = ) P 0 ⇤ ♦ I ` [P 0] C [P] I ` [P] while B do C [P ^ ¬B]
(WHILE)
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning.
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
P ^ B = ) P 0 ⇤ ♦ I ` [P 0] C [P] I ` [P] while B do C [P ^ ¬B]
(WHILE)
Quantitative Reasoning in Separation Logic
Total correctness: if program C is started in a state which satisfies P then C terminates and Q holds after the execution. Frame rule for modular and local reasoning. Consume one token in each iteration
[P] C [Q] [P ∗ R] C [Q ∗ R]
(FRAME)
[P] C [Q] conjunction ♦ ⇤ P,
(Affine) predicate for tokens:
en ♦ found
⇐ ⇒
ens ♦ ⇤ . . . ⇤ ♦ by ♦n. Appendix IV.
Concurrent Separation Logic
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate. Describes the shared memory.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate. Describes the shared memory. Shared memory can only be accessed in atomic blocks.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate. Describes the shared memory. Shared memory can only be accessed in atomic blocks. Assume invariant I holds. Ensure invariant I holds.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate. Describes the shared memory. Shared memory can only be accessed in atomic blocks. Assume invariant I holds. Ensure invariant I holds. Parallel composition.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Concurrent Separation Logic
Resource invariant: another predicate. Describes the shared memory. Shared memory can only be accessed in atomic blocks. Assume invariant I holds. Ensure invariant I holds. Parallel composition. Modular reasoning.
xtension to CCRs is possible. judgment I ` [P] C [Q] , in a state where emp ` [P ⇤ I] C [Q ⇤ I] I ` [P] atomic{C} [Q]
(ATOM)
I ` [P1] C1 [Q1] I ` [P2] C2 [Q2] I ` [P1 ⇤ P2] C1 k C2 [Q1 ⇤ Q2]
(PAR)
Back to the Stack: Formal Specification
Resource Invariant Specifications of Push and Pop I ,
- 9u. S 7! u ⇤
~
0i<n α(i, u)
α(i, u) , 9a, c. C[i] 7!
r c ⇤ A[i] 7! r a ⇤ (c = 0 _ a = u _ ⌃)
I ` [A[tid] 7!
r
⇤ C[tid] 7!
r
⇤ ⌃n] push(v) [A[tid] 7!
r
⇤ C[tid] 7!
r
] ` 7! ⇤ 7! ⇤ 7! ⇤ 7! I ` [A[tid] 7!
r
⇤ C[tid] 7!
r
⇤ ⌃n] pop() [A[tid] 7!
r
⇤ C[tid] 7!
r
]
Back to the Stack: Formal Specification
Resource Invariant Specifications of Push and Pop For every thread i: if i has read the stack pointer then it is either unchanged or their is a token available. I ,
- 9u. S 7! u ⇤
~
0i<n α(i, u)
α(i, u) , 9a, c. C[i] 7!
r c ⇤ A[i] 7! r a ⇤ (c = 0 _ a = u _ ⌃)
I ` [A[tid] 7!
r
⇤ C[tid] 7!
r
⇤ ⌃n] push(v) [A[tid] 7!
r
⇤ C[tid] 7!
r
] ` 7! ⇤ 7! ⇤ 7! ⇤ 7! I ` [A[tid] 7!
r
⇤ C[tid] 7!
r
⇤ ⌃n] pop() [A[tid] 7!
r
⇤ C[tid] 7!
r
]
Verifying Treiber’s Stack
While loop of push
S := alloc(1); [S] := 0; A := alloc(max_tid); C := alloc(max_tid); push(v) , pushed := false; x := alloc(2); [x] := v; [(pushed _ ⌃n) ⇤ γr(tid, _, _)] // loop invariant while ( !pushed ) do { //While rule antecedent: ((pushed _ ⌃n) ⇤ γr(tid, _, _)) ^ !pushed ) ⌃n1 ⇤ γr(tid, _, _) ⇤ ⌃ [⌃n1 ⇤ γr(tid, _, _)] atomic { [⌃n1 ⇤ γr(tid, _, _) ⇤ S 7! u ⇤ α(tid, u) ⇤ I0(tid, u)] // atom block [⌃n1 ⇤ γ(tid, _, _) ⇤ S 7! u ⇤ I0(tid, u)] // impl. & read perm. t := [S]; [⌃n1 ⇤ γ(tid, _, _) ⇤ (S 7! u ^ t = u) ⇤ I0(tid, u)] // read & frame C[tid] := 1 [⌃n1 ⇤ γ(tid, _, 1) ⇤ (S 7! u ^ t = u) ⇤ I0(tid, u)] // assignment A[tid] := t [⌃n1 ⇤ (A[tid] 7! t ^ t = u) ⇤ C[tid] 7! 1 ⇤ S 7! u ⇤ I0(tid, u)] [⌃n1 ⇤ γr(tid, t, 1) ⇤ S 7! u ⇤ α(tid, u) ⇤ I0(tid, u)] // perm. [⌃n1 ⇤ γr(tid, t, 1) ⇤ I] // exist. intro & (3) }; [⌃n1 ⇤ γr(tid, t, 1)] // atomic block & frame // [x+1] := t; this is not essential for lock-freedom atomic { [⌃n1 ⇤ γr(tid, t, 1) ⇤ I] // atomic block [⌃n1 ⇤ γr(tid, t, 1) ⇤ S 7! u ⇤ ~1in α(i, u)] // exist. elim. s := [S]; if s == t then { [⌃n1 ⇤ γ(tid, _, _) ⇤ S 7! t ⇤ ~{1,...,n}\{tid}(γ(i, _, _))] [S] := x; [γ(tid, _, _) ⇤ S 7! x ⇤ I0(tid, x)] // permissions & (4) pushed := true [(pushed _ ⌃n) ⇤ γ(tid, _, _) ⇤ 9u. S 7! u ⇤ I0(tid, u)] } else { [⌃n1 ⇤ t 6= u ^ γr(tid, t, 1) ⇤ α(tid, u) ⇤ S 7! u ⇤ I0(tid, u)] [⌃n ⇤ γ(tid, t, 1) ⇤ S 7! u ⇤ I0(tid, u)] // impl. using (5) skip [(pushed _ ⌃n) ⇤ γ(tid, _, _) ⇤ 9u. S 7! u ⇤ I0(tid, u)] }; C[tid] := 0 [(pushed _ ⌃n) ⇤ γ(tid, _, 0) ⇤ S 7! u ⇤ I0(tid, u)] // write & exist. elim (above) and permissions & impl. [(pushed _ ⌃n) ⇤ γr(tid, _, _) ⇤ α(tid, u) ⇤ S 7! u ⇤ I0(tid, u)] [(pushed _ ⌃n) ⇤ γr(tid, _, _) ⇤ I] // exist. intro }; [(pushed _ ⌃n) ⇤ γr(tid, _, _)] // atomic block end }
Data Structure Tokens Per Operation Treiber’s Stack n Michael and Scott’s Queue n + 1 Hazard-Pointer Stack n + (c · n) Hazard-Pointer Queue (n + 1) + (c · n) Elimination-Backoff Stack n · (n + 1)
More Advanced Shared- Memory Data Structures
Details are in the Paper
Conclusion and Ongoing Work
- Compensation schemes simplify reasoning about lock-freedom
- Quantitative reasoning can be directly integrated in modern logics for
safety properties
- The reasoning works for involved real-world data structures
- Current work: Adapt quantitative reasoning to prove termination-
sensitive contextual refinement
- Future work: quantitative reasoning for classic lock-based
synchronization
Conclusion and Ongoing Work
- Compensation schemes simplify reasoning about lock-freedom
- Quantitative reasoning can be directly integrated in modern logics for
safety properties
- The reasoning works for involved real-world data structures
- Current work: Adapt quantitative reasoning to prove termination-
sensitive contextual refinement
- Future work: quantitative reasoning for classic lock-based