PowerPoint Presentation by AM3020p

VIEWS: 7 PAGES: 59

									 Proving Data Race Freedom in
   Relaxed Memory Models
                  Beverly Sanders

Computer & Information
Science & Engineering
                    Overview
    • Motivation
    • Memory models and data races
    • Simple multi-threaded programming
      language and logic
    • Extensions to allow reasoning about data
      races
    • Example
2   • Conclusions and future work
    Concurrent programming is hard
    • non-deterministic
    • non-reproducible
    • operational reasoning unreliable

    • It is becoming more commonplace
    • It is getting harder—relaxed
3     memory models
           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
4       execute in the order specified by the program
        Modern systems are not SC
    • Optimizations (both by compiler and
      hardware) that preserve semantics of
      sequential programs may violate SC
      – reorder statements
      – buffered writes
      –…

5
                     Example
            Initially:
                int x = 0;
                boolean done = false

    Thread 0:                 Thread 1:
           …                      int r;
           x := ….                …
           done := true;          while (!done){/*busywait*/}
6
           …                      r := x;
                                  …
    Statements
    reordered.
                                Example
    OK in              Initially:
    sequential
                           int x = 0;
    program but
    not in this one        boolean done = false

            Thread 0:                    Thread 1:
                      …                      int r;
                      done := true;          …
                      x := ….                while (!done){/*busywait*/}
7
                      …                      r := x;
                                             …
                              Loop optimized
                              by compiler.
                     Example  Ok in
                              sequential
            Initially:
                              program but not
                int x = 0;    this one
                boolean done = false

    Thread 0:                 Thread 1:
           …                      …
           x := ….                r0 = done;
           done := true;          while (!r0){/*busywait*/}
8
           …                      r := x;
                                  …
                          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
9        program is sufficiently constrained
                    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
10        it is hard to understand
              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
11      – happened-before is transitive
             Thread 0   Thread 1


       write x


     write done
                                   read done

                                   read x


12

                          happened before edge
             Thread 0   Thread 1
                                     done is volatile
       write x


     write done
                                   read done

                                   read x


13

                          happened before edge
             Thread 0   Thread 1
                                     done is volatile
       write x


     write done
                                   read done

                                   read x


14

                          happened before edge
              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.
15
             Thread 0   Thread 1


       write x


     write done
                                   read done

                                   read x


16

                          happened before edge
                          data race
             Thread 0   Thread 1
                                     done is volatile
       write x


     write done
                                   read done

                                   read x


17

                          happened before edge
             Fundamental property
     • If there are no data races in any sequentially
       consistent execution, then the program will
       behave as if it is sequentially consistent.




18
     • 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
19     behavior of programs with data races
                           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
20
        – Rules out important programming idioms
         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
21     – Prove in the usual way
             Simple multithreaded
            programming language

     Program ::= Global* Volatile* Thread+
     Thread ::= ThreadID Local* Stmt
     Stmt ::= …

     (no procedures or objects)
22
     Stmt ::=
         Var ::= E
        | assume E
        | assert E
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
23      | < Stmt >
                         If E's value is true,
     Stmt ::=            continue, otherwise
         Var ::= E       computation gets stuck.
        | assume E
        | assert E       Getting stuck is OK
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
24      | < Stmt >
                         If E's value is true,
     Stmt ::=            continue, otherwise
         Var ::= E       computation "goes
        | assume E       wrong".
        | assert E
                         Going wrong is NOT OK
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
25      | < Stmt >
                         Set the values of the given
     Stmt ::=            Vars to arbitrary values of
         Var ::= E       their types
        | assume E
        | assert E
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
26      | < Stmt >
                         Nondeterministic choice
     Stmt ::=
         Var ::= E
        | assume E
        | assert E
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
27      | < Stmt >
                         Sequential composition
     Stmt ::=
         Var ::= E
        | assume E
        | assert E
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
28      | < Stmt >
                         Statement is executed
     Stmt ::=            atomically
         Var ::= E
        | assume E
        | assert E
        | havoc Var+
        | skip
        | Stmt [] Stmt
        | Stmt ; Stmt
29      | < Stmt >
     • This language is more expressive than it
       appears at first glance

                if E then S0 else S1

          can be represented as

              assume E ; S0
            []
30             assume ~E ; S1
           while E do S
     with invariant I, where S modifies only
      variables in M


        assert I;
        havoc M;
        assume I;
           assume E ; S0; assert I; assume false;
31
        []
           assume ~E;
         Weakest preconditions
     wp.v := e.Q ≡ [e\v]Q
     wp.assume e.Q ≡ e  Q
     wp.assert e.Q ≡ e /\ Q
     wp.havoc v.Q ≡ 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)
32
     • {P} S {Q} ≡ P  wp.S.Q

     • wp.S.Q0 /\ Q1 = wp.S.Q0 /\ wp.S.Q1




33
                 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,
34     and havoc commands are implicitly atomic or are
       contained in an explicit atomic statement
                              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
35
      (SC) Multithreaded Correctness
     Thread0     Thread1

     {Initial}   {Initial}   1. Show each
     <S0>        <T0>           thread's proof
     {P1}        {Q1}
                                outline is valid
     <S1>        <S1>
     ..          ..          2. Show non-
     {Pn}        {Qm}
     <Sn>        <Sm>
                                interference
36   {true}      {true}         (Owicki-Gries)
      (SC) Multithreaded Correctness
     Thread0     Thread1     • Non-interference:
                               no assertion in
     {Initial}   {Initial}     one thread is
     <S0>        <T0>
                               violated by an
     {P1}        {Q1}
     <S1>        <S1>
                               action in another.
     ..          ..
                             • Need to check all
     {Pn}        {Qm}
     <Sn>        <Sm>          assertions
37   {true}      {true}        between atomic
                               actions
      (SC) Multithreaded Correctness
     Thread0     Thread1

     {Initial}   {Initial}   Example: To show that
     <S0>        <T0>          S1 in Thread0 does
     {P1}        {Q1}          not falsify Qm in
     <S1>        <S1>          the proof outline of
     ..          ..
     {Pn}        {Qm}
                               Thread1
     <Sn>        <Sm>
38   {true}      {true}
                             {P1 /\Qm}S1{Qm}
             Extensions for memory-
              synchronization state

     let h: globals+volatile+thread →
              {globals+volatile+thread}

     where                                norace('th','v')
        'v'  h(' th' )

39
     means that thread th can access v without
     causing a data race
                            Updates to h
     release ('t' , 'v' ) h 
        λ'z'. if 'v' 'z'  then h(' t' )  h(' v' ) else h(' v' )

     acquire ('t' , 'v' ) h 
       λ'z'. if 't' 'z'  then h(' t' )  h(' v' ) else h(' t' )

     invalidate ('t' , 'v' ) h 
40      λ'z'. if 't' 'z'   'v' 'z'  then h( 'z' ) else h( 'z' ) \ {' v' }
                            Updates to h := h('v') U h('t')
                                     h('v')

     release ('t' , 'v' ) h 
        λ'z'. if 'v' 'z'  then h(' t' )  h(' v' ) else h(' v' )
                                                 h('t') := h('v') U h('v')
     acquire ('t' , 'v' ) h 
        λ'z'. if 't' 'z'  then h(' t' )  h(' v' ) else h(' t' )

     invalidate ('t' , 'v' ) h 
41      λ'z'. if 't' 'z'   'v' 'z'  then h( 'z' ) else h( 'z' ) \ {' v' }
                     ('z':'z' 'v' , 'z' 't ': h('z' ) : h('z' ) \ {'v'})
      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


42
                Volatile variables
     • reading a volatile variable
       <acquire (curr,'x‘); r := x;>

     • writing a volatile variable x
       <x := e; release(curr,'x')>
43
       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')
44
     >
                         Locks
     • Lock the lock variable lck
     <acquire(curr,'lck'); lck.lock;>

     • Unlock lck
     <lck.unlock; release(curr,'lck')>

45
                     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');
46                  {norace('t1','y')}
                   Proof rules


     {norace('t1','y') /\ ('t0' = 't1' \/ 'x' ≠ 'y')}
                 invalidat('t0','x')
                 {norace('t1','y')}


47
                 Recall example
             Initially:
                 int x = 0;
                 boolean done = false

     Thread 0:                 Thread 1:
            …                      int r;
            x := ….                …
            done := true;          while (!done){/*busywait*/}
48
            …                      r := x;
                                   …
     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;                        done not
        invalidate(‘Thread0’,'done');         volatile
       >
49     {true}
Thread 1:              Thread 1:
  int r;
                            r0 := done;
     …
                                assume !r0;
     while (!done){}
     r := x;                    assume false;
     …                     []
                                assume r0;
                           r := x
50
     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;
51
         {norace(Thread1,x)}
        < assert norace(Thread1,x); r := x   >
     • 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’) /\
52
                done => norace(‘Thread1’,’x’)}
     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 is
       >                                 volatile
       {done /\ norace(‘done’,’x’}
53   => {true}
     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)}
54      < assert norace(Thread1,x); r := x   >
     • 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;                                   Precondition is
        invalidate(‘Thread0’,'x')                   false, triple
       >                                          holds trivially
55
     {r0 /\ r0 => done /\ r0 => norace(Thread1,x)}
                        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
56
     • 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
57         • “effectively immutable x signaled by done”
        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
58   • Tool support
          The end

     Thanks for attending

59

								
To top