3 Thread safety notation

Notation to indicate the thread-safety-ness of a set of functions

Definition : For functions X,Y, X1...Xn, Y1...Ym we write

     X|Y                        if it is permissible for one thread to call X while another thread calls Y.
     X1|X2|...|Xn               if Xi|Xj for all i != j
     (X1,...,Xn)|(Y1,...,Ym)    if Xi|Yj for all i,j
     ||(X1,...,Xn)              if Xi|Xj for all i,j   i.e.   (X1,...,Xn) | (X1,...,Xn)

     X$Y                        if it is NOT permissible for one thread to call X while another thread calls Y.
                                It is sufficient for example that a single thread calls X then Y or vise versa.
                                Alternatively if there are multiple threads involved then a mutex should be used.
                                The mutex ensures that a call to X comes strictly before or after a call to Y 
                                (i.e. the calls are serialised), and that any changes made by one thread are 
                                fully visible to another.
                                
     X1$X2$...$Xn               if Xi$Xj for all i != j
     (X1,...,Xn)$(Y1,...,Ym)    if Xi$Yj for all i,j
     $$(X1,...,Xn)              if Xi$Xj for all i,j   i.e.   (X1,...,Xn) $ (X1,...,Xn)

    1-2:X                       X can be called once or twice
    -1:X                        if X can be called at most once
     1:X                        if X must be called exactly once
      !x                        if x must be called exactly once
    1+:X                        at least one call to X
    0+:X                        X can be called any number of times (the default)
    
    X < Y                       X must be called before Y
                                If multiple threads are involved then memory barriers must be used to ensure
                                that any changes made by the thread calling X become visible to the thread calling
                                Y.
                                
    (X1,...,Xn)<(Y1,...,Ym)     if Xi < Yj for all i,j

In this notation, X can be used in place of (X) - i.e. when a single function appears in brackets, the brackets are not necessary. For example X|(Y1...Ym) means X|Yj for all j.

It is assumed that ||(X1,...,Xn) can be nested within a larger expression, with the understanding that ||(X1,...,Xn) is equivalent to (X1,...,Xn) in the context of the surrounding expression.

Similarly $$(X1,...,Xn) is equivalent to (X1,...,Xn) when it appears inside a larger expression.

X$Y and X|Y are interpreted as (X,Y) within the context of the containing expression.

Laws

    x $ y = y $ x
    x | y = y | x
    
    x $ x = $$(x)
    x | x = ||(x)

    $$(x,y)  <=>  $$(y,x)
    $$(x,y)   =>  $$(x)

    ||(x,y)  <=>  ||(y,x)
    ||(x,y)   =>  ||(x)
    
    x  x  x   x   not(x  e

    not x$y  <=>  x|y
    not x|y  <=>  x$y
    
    x|y     =>   not(x  x $ y
    x < y  =>  not (y < x)          converse false!
    
    $$( (x,y),z ) =  (x,y)$(x,y) and (x,y)$z
                  =  x$x and x$y ...
                  ...
                  = (x,y,z) $ (x,y,z)
                  = $$(x,y,z)

    e and not e  => false
    x|y and x$y  => false
    $$(||(x))  =>  false
    ||($$(x))  =>  false

Example: shared read, exclusive write

Let there be a variable where

    C denotes the constructor
    D denotes the destructor
    M1,...Mm denote functions that modify the variable
    R1,...,Rn denote functions that read the variable

The variable is threadsafe using the following rule:

    !C < (||(R1,...,Rn) $ $$(M1,...Mm)) < !D
    
which means

    C can be called at most once
    C < Ri   for all i
    C < Mi   for all i
    Ri | Rj  for all i,j
    Ri $ Mj  for all i,j
    Mi $ Mj  for all i,j
    Ri < D   for all i
    Mi < D   for all i
    D can be called at most once

Note that we cannot write it like this:

    $$(M1,...Mm,||(R1,...,Rn))
    
This is illogical because it implies

   ||(R1,...,Rn) $ ||(R1,...,Rn)
   
which is self contradictory, because it implies Ri | Rj and also Ri $ Rj.

Sufficient requirements for threadsafe access to a variable

Let the thread safety rules by given by a single expression e.

1.  If e = (e1 < e2),  then recursively check both e1 and e2

2.  if e = $$(m1,...,mn) where each mi are functions then e is safe.

3.  if e = e1 $ e2 then recursively check both e1 and e2

4.  if e = ||(r1,...,rn) then safe as long as each ri only have read access.

5.  e = !x then e is safe

Algebraic rules

    expr1 and expr2     means it is known that the rules implied by expr1 as well as the rules 
                        implied by expr2 must be satisfied.
                        
    expr1 or expr2      means it is known that either the rules implied by expr1 or else the rules 
                        implied by expr2 must be satisfied.
                        
    not expr            means it is known that the rules implied by expr do not need to be 
                        satisfied.

and is commutative and transitive
or is commutative and transitive
not(not p) = p

Y < X   => not(X < Y).  But converse is false!

    X$Y     means       X
    

Example

$$W | $$X | $$(Y,Z) is shorthand for

    W $ W
    X $ X
    Y $ Y
    Y $ Z
    Z $ Z
    W | X
    W | Y
    W | Z
    X | Y
    X | Z