Proving Data Race Freedom in Relaxed Memory Models
Post on 01-Jan-2016
19 Views
Preview:
DESCRIPTION
Transcript
Proving Data Race Freedom in Relaxed Memory Models
Beverly Sanders
Computer & Information Science & Engineering
2
Overview
• Motivation• Memory models and data races• Simple multi-threaded programming
language and logic• Extensions to allow reasoning about data
races• Example• Conclusions and future work
3
Concurrent programming is hard
• non-deterministic• non-reproducible• operational reasoning unreliable
• It is becoming more commonplace• It is getting harder—relaxed
memory models
4
Sequential consistency
• Virtually all approaches for reasoning about concurrent program, both formal and informal assume SC
• Lamport(1979)– All operations appear to execute in some
sequential order– All operations on a single thread appear to
execute in the order specified by the program
5
Modern systems are not SC
• Optimizations (both by compiler and hardware) that preserve semantics of sequential programs may violate SC– reorder statements– buffered writes– …
6
ExampleInitially:
int x = 0;
boolean done = false
Thread 0:
…
x := ….
done := true;
…
Thread 1:
int r;
…
while (!done){/*busywait*/}
r := x;
…
7
ExampleInitially:
int x = 0;
boolean done = false
Thread 0:
…
done := true;
x := ….
…
Thread 1:
int r;
…
while (!done){/*busywait*/}
r := x;
…
Statements reordered. OK in sequential program but not in this one
8
ExampleInitially:
int x = 0;
boolean done = false
Thread 0:
…
x := ….
done := true;
…
Thread 1:
…
r0 = done;
while (!r0){/*busywait*/}
r := x;
…
Loop optimized by compiler. Ok in sequential program but not this one
9
Solutions?
• Bad solution: require system to implement SC– unacceptable loss of performance
• Better: provide mechanisms for the programmer to constrain non-SC behavior– explicit “fence” or “memory barrier” instructions– intrinsic
• lock and unlock instructions• volatile variables
– But then we should be able to verify that program is sufficiently constrained
10
Memory model
• Specification of how threads interact with the memory
• Traditionally, memory models have been specified for architectures
• Recently, memory models have become part of the programming language semantics.– Java is the most ambitious attempt to date
– Seems simple at first glance, but experience has shown it is hard to understand
11
Simple memory model
• Based on the happened-before relation (Lamport)• Inspired by the Java Memory Model
– an event happens-before another event on the same thread if it occurs earlier in the program order.
– writing a volatile variable happens-before subsequent reads of the variable
– unlocking a lock happens-before subsequent locks on the same lock
– happened-before is transitive
12
Thread 0 Thread 1
write x
write doneread done
read x
happened before edge
13
Thread 0 Thread 1
write x
write doneread done
read x
happened before edge
done is volatile
14
Thread 0 Thread 1
write x
write doneread done
read x
happened before edge
done is volatile
15
Definition of data race
• Conflicting accesses to the same variable that are not ordered by happens-before – Accesses to a variable conflict if they are performed by
different threads and at least one is a write
– Remark: term “race” is overloaded.– Sometimes it means any undesirable concurrency caused
nondeterminism (which often comes from not using locks properly to enforce atomicity)
– A program with no data races may still exhibit undesirable non-determinism.
16
Thread 0 Thread 1
write x
write doneread done
read x
happened before edgedata race
17
Thread 0 Thread 1
write x
write doneread done
read x
happened before edge
done is volatile
18
Fundamental property
• If there are no data races in any sequentially consistent execution, then the program will behave as if it is sequentially consistent.
19
• What about programs with data races?– Some systems leave behavior undefined– The Java memory model also constrains the
behavior of programs with data races• includes a notion of causality
• no "out of thin air" values
• safety
• Our goal is to prove the absence of data races, so we are not concerned with the behavior of programs with data races
20
Difficulties• It is difficult to reason about partial orders imposed
on execution paths• Need data race freedom on all paths• In most work, one settles for satisfying sufficient
constraints• all accesses to a particular shared variable are protected by a
common lock
• variable is volatile
– Even "simple" rules are difficult to get right
– Rules out important programming idioms
21
Proving data race freedom
• Extend known assertional methods – Start with very simple multithreaded
programming language (similar to Plato and BoogiePL)
– Extend the state space with a "happened-before" function that tracks information about happened-before edges
– Race-free access can be expressed as assertion– Prove in the usual way
22
Simple multithreaded programming language
Program ::= Global* Volatile* Thread+
Thread ::= ThreadID Local* Stmt
Stmt ::= …
(no procedures or objects)
23
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
24
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
If E's value is true, continue, otherwise computation gets stuck.
Getting stuck is OK
25
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
If E's value is true, continue, otherwise computation "goes wrong".
Going wrong is NOT OK
26
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
Set the values of the given Vars to arbitrary values of their types
27
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
Nondeterministic choice
28
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
Sequential composition
29
Stmt ::=
Var ::= E
| assume E
| assert E
| havoc Var+
| skip
| Stmt [] Stmt
| Stmt ; Stmt
| < Stmt >
Statement is executed atomically
30
• This language is more expressive than it appears at first glance
if E then S0 else S1
can be represented as
assume E ; S0 [] assume ~E ; S1
31
while E do Swith invariant I, where S modifies only
variables in M
assert I; havoc M; assume I; assume E ; S0; assert I; assume false; [] assume ~E;
32
Weakest preconditions
wp.v := e.Q ≡ [e\v]Q
wp.assume e.Q ≡
wp.assert e.Q ≡ e /\ Q
wp.havoc v.Q ≡
wp.skip.Q ≡ Q
wp.s0 [] s1.Q ≡ wp.s0.Q /\ sp.s1.Q
wp.s0 ; s1.Q ≡ wp.s0.(wp.s1.Q)
Qe
Q:v
33
• {P} S {Q} ≡
• wp.S.Q0 /\ Q1 = wp.S.Q0 /\ wp.S.Q1
Q.wp.SP
34
Atomic Statements
• Implicitly atomic statements– satisfy "at most once rule"
• statement accesses as most one non-local variable at most once
• Explicitly atomic statement< S >
Assumption that must be satisfied by the implementation
• Well formed program: all assign, assert, assume, and havoc commands are implicitly atomic or are contained in an explicit atomic statement
35
Locks
• Can be specified using explicit atomic statements
lck : ThreadId + free
lck.lock = <assume lcl=free; lck := curr>lck.unlock = <assert lck = curr; lck := free>
where curr = current thread
36
(SC) Multithreaded Correctness
Thread0
{Initial}<S0>{P1}<S1>..{Pn}<Sn>{true}
Thread1
{Initial}<T0>{Q1}<S1>..{Qm}<Sm>{true}
1. Show each thread's proof outline is valid
2. Show non-interference (Owicki-Gries)
37
(SC) Multithreaded Correctness
Thread0
{Initial}<S0>{P1}<S1>..{Pn}<Sn>{true}
Thread1
{Initial}<T0>{Q1}<S1>..{Qm}<Sm>{true}
• Non-interference: no assertion in one thread is violated by an action in another.
• Need to check all assertions between atomic actions
38
(SC) Multithreaded Correctness
Thread0
{Initial}<S0>{P1}<S1>..{Pn}<Sn>{true}
Thread1
{Initial}<T0>{Q1}<S1>..{Qm}<Sm>{true}
Example: To show that S1 in Thread0 does not falsify Qm in the proof outline of Thread1
{P1 /\Qm}S1{Qm}
39
Extensions for memory-synchronization state
let h: globals+volatile+thread → {globals+volatile+thread}
where
)th'h('v''
means that thread th can access v without causing a data race
norace('th','v')
40
Updates to h
)t'h('else)v'h(')t'h('thenz''t''ifz'.λ'
h)v'',t'('acquire
)v'h('else)v'h(')t'h('thenz''v''ifz'.λ'
h)v'',t'('release
}v'{'\)z''h(else)z''h(thenz''v''z''t''ifz'.λ'
h)v'',t'('invalidate
41
Updates to h
)t'h('else)v'h(')t'h('thenz''t''ifz'.λ'
h)v'',t'('acquire
)v'h('else)v'h(')t'h('thenz''v''ifz'.λ'
h)v'',t'('release
}v'{'\)z''h(else)z''h(thenz''v''z''t''ifz'.λ'
h)v'',t'('invalidate
h('v') := h('v') U h('t')
h('t') := h('v') U h('v')
})''{\)''h( :)''h(:'''','''':''( vzztzvzz
42
Modify programming language
• add (function valued) ghost variable h
• statements replaced with new statements that also read and/or update h
• if the modified program does not "go wrong", it is free of data races
43
Volatile variables
• reading a volatile variable
<acquire (curr,'x‘); r := x;>
• writing a volatile variable x
<x := e; release(curr,'x')>
44
Global (non-volatile) variables
• reading global n
< assert norace(curr, 'n'); r := n; >
• writing global n
< assert norace(curr,'n'); n := r; invalidate(curr,'n')>
45
Locks
• Lock the lock variable lck
<acquire(curr,'lck'); lck.lock;>
• Unlock lck
<lck.unlock; release(curr,'lck')>
46
Proof rules
{norace('t1','y') \/ ('t0'='t1' /\ norace('x','y'))}
acquire('t0','x');
{norace('t1','y')}
{norace('t1','y') \/ ('x'='t1' /\ norace('t0','y'))}
release('t0','x');
{norace('t1','y')}
47
Proof rules
{norace('t1','y') /\ ('t0' = 't1' \/ 'x' ≠ 'y')}
invalidat('t0','x')
{norace('t1','y')}
48
Recall exampleInitially:
int x = 0;
boolean done = false
Thread 0:
…
x := ….
done := true;
…
Thread 1:
int r;
…
while (!done){/*busywait*/}
r := x;
…
49
Thread 0:{norace('Thread0','done') /\ norace('Thread0','x')<assert norace(Thread0’,'x');
x := 1; invalidate(Thread0’,'x') > {norace(Thread0,done)}
<assert norace(‘Thread0’,'done'); done := true; invalidate(‘Thread0’,'done'); >
{true}
done not volatile
50
Thread 1:
int r;
…while (!done){}
r := x;
…
Thread 1:
r0 := done;
assume !r0;
assume false;
[]
assume r0;
r := x
51
Thread 1:{norace(Thread1,done) /\
done => norace(Thread1,’x’)}
<assert norace(Thread1,done); r0 := done;>
{r0 => norace(Thread1,x)}
assume !r0; assume false;
[]
{r0 => norace(Thread1,x)}
assume r0;
{norace(Thread1,x)}
< assert norace(Thread1,x); r := x >
52
• Both proof outlines are valid in isolation, but not interference-free
• For example, this proof obligation does not hold
{norace(‘Thread0’,’done’) /\ norace(‘Thread1’,’done’) /\
done => norace(‘Thread1’,’x’)} <…..invalidate(curr,'done'); >{norace(‘Thread1’,’done’) /\
done => norace(‘Thread1’,’x’)}
53
Thread 0:{!done /\ norace('Thread0','x')<assert norace(Thread0’,'x');
x := 1; invalidate(‘Thread0’,'x') > {!done /\ norace(Thread0,done)} < done := true; release(‘Thread0’,'done'); >
{done /\ norace(‘done’,’x’} => {true}
done isvolatile
54
Thread 1:{done => norace(‘done’,’x’)}
<acquire(Thread1,’done’); r0 := done;>
{r0 => done /\ r0 => norace(Thread1,x)}
assume !r0; assume false;
[]
{r0 => done /\ r0 => norace(Thread1,x)}
assume r0;
{r0 /\ r0 => done /\ norace(Thread1,x)}
< assert norace(Thread1,x); r := x >
55
• Both proof outlines are valid in isolation• They are also interference-free• Example
{!done /\ norace('Thread0','x') /\ r0 /\ r0 => done /\ r0 =>norace(Thread1,x)}
<assert norace(Thread0’,'x'); x := 1; invalidate(‘Thread0’,'x') >
{r0 /\ r0 => done /\ r0 => norace(Thread1,x)}
Precondition is false, triple
holds trivially
56
Remarks• Other approaches to data race detection
cannot handle this example
• They try to show– locking protocol followed– shared variables are volatile
• This framework allows other information (invariants) to be incorporated into the analysis by strengthening assertions
57
• How can we find the strengthened assertions?– Just calculating wp from postcondition of true may not
be sufficient.
– Let the programmer provide it (like is sometimes done for loop invariants)
– Heuristics• track values (done)
• conjoined assumed value (r0)
• using the important invariants of program – (r0 => done)
– done => norace(‘done’,’x’)
– Identify common patterns• “effectively immutable x signaled by done”
58
Conclusions and future work
• More complete language model– Procedures – Objects
• h:location -> {location}– Byte code logic– Java memory model mentions
• static initialization• final fields• dynamic thread creation, join,…
• Reduce interference• Tool support
59
The end
Thanks for attending
top related