Understanding Concurrency: Key Concepts in Process and Thread Management
This comprehensive overview of concurrency introduces fundamental concepts related to processes and threads in computing. It explores important terms such as multiprocessing, multi-programming, atomic operations, and critical sections, emphasizing the significance of safety and liveness properties in concurrent programming. The guide also covers the implementation of critical sections to ensure atomic execution of instructions, while discussing the producer-consumer problem as a typical scenario requiring careful management of resource sharing. Ideal for students and professionals seeking to enhance their understanding of concurrent programming.
Understanding Concurrency: Key Concepts in Process and Thread Management
E N D
Presentation Transcript
Concurrency • A process is a program executing on a virtual computer • Processor speed and multiplexing of shared resources are ignored • Order of thread execution is non-deterministic • Multiprocessing • A system may contain multiple processors on which cooperating threads/processes can execute simultaneously • Multi-programming • Thread/process execution can be interleaved on a single processor because of time-slicing
The Basic Issue • Operations are not atomic • An atomic operation is one that executes to completion or does not execute at all • An atomic operation has “an all or nothing” flavor: • Either it executes to completion, or • Does not execute at all, and • No one can see a partially-executed state
Critical Sections • A critical section is: • Consecutive program instructions • all instruction executes atomically • A critical section implementation must allow only one thread to execute in the critical section at any given time • A good implementation • Allows maximum concurrency while preserving correctness
Implementation • Permit access to shared variables only within a critical section • General program structure Entry section Wait if already locked “Lock” Critical section code Exit critical section “Unlock”
Properties • Concurrent programs are specified using properties, which is a predicate that evaluated over a run of the concurrent program. • Thus, it has the value true or false in each run of the program. • We say that the property holds if it is true in each run. • A property: the value of x is always at least as large as the value of y, and x has the value of 0 at least once. • Not a property: the average number of processes waiting on a lock is less than 1.
Safety and liveness • Any property is either • a safety property, • A liveness property, or • a conjunction of a safety and a liveness property.
Safety • A safety property is of the form nothing bad happens (i.e., all states are safe). • Examples: • The number of processes in a critical section is always less than 2. • Let p be the sequence of produced values and c be the sequence of consumed values. c is always a prefix of p.
Liveness • A liveness property is of the form something good happens(i.e., a state is eventually achieved). • Examples: • A process that wishes to enter the critical section eventually enters. • p grows without bound. For every value x in p, x is eventually in c.
Safety and Liveness • Showing a safety property P holds: • find a safety property P’: P’ => P; • show that P’ initially holds; • show that each step of the program maintains P’. • Showing a liveness property holds is usually done by induction.
Basic Properties • Finite progress axiom Each process takes a step infinitely often. • Atomic shared variables Consider {x = A} any concurrent read of x will return x = B; either A or B. {x = B} {x = 0} cobegin x = x + 1; || x = x - 1; coend {x ∈ {-1, 0 1} }
Producer/Consumer • Let p be the sequence of produced values and c be the sequence of consumed values. • c is always a prefix of p. • For every value x in p, x is eventually in c. • Bounded buffer variant: • Always |p| − |c| ≤ max
Producer Active proctype producer () { do :: (turn== P) -> printf(“Produce\n”) turn = C od }
Consumer Active proctype consumer() { do :: (turn== C) -> printf(“\tConsumer\n”) turn = P od }
Correctness Conditions • A correct solution to the critical section problem must satisfy: • Safety: • Nothing bad ever happens! • At most one thread may be executing in the critical section (mutual exclusion) • Liveness: • Eventually something good happens!! • If one of more threads are in their entry section, then eventually at least one of them enters the critical section. • Further requirement: • Bounded waiting: If a thread iis in entry section, then there is a bound on the number of times that other threads are allowed to enter the critical section before thread i’ s request is granted
Step by Step • Initial value of turn is p • At least one producer will find the guard :: (turn== P) -> to be true • So, using UNIX: Spin prodcons.pml | more Produce Consume Produce Consume • Not using UNIX: Spin –u14 prodcons.pml Produce Consume Produce Consume ----------------- Depth-limit (-u14 steps) reached
Extensing the Example • Instantiate more than 1 process Active [2] proctype producer() {…} • Now we may violate CS by have 2 procucers in the CS [::(turn== P) ->]
Revised Producer Consumer Example mtype = { P,C,N}; mtype turn=P; pid who; inline request(x,y) { atomic(x=y; who=0} } Inline release(x,y) { atomic {x=y; who=0} }
Main Code Active [2] proctype producer() { do :: request (turn, P, N) -> printf (“P%d\n”,_pid); assert (who== _pid); release (turn, C) od } Active [2] proctype consumer() { do :: request (turn, C, N) -> printf (“C%d\n”,_pid); assert (who== _pid); release (turn, P) od }
Printout Spin prodcons2.pml P1 C2 P1 C3 P0 C3 P1 C3 … • There is some non determinism in the model, since both consumers share the same guard condition
Further Simulation init{ assert (false) } spin false.pml spin: line 1 “false.pml“, Error: assertion violated • A simulation is not a proof! • To prove that, invoke SPIN in verification mode spin –a prodcons2.pml #generate a verifier cc –o pan pan.c #compile the verifier
./pan #perform the verification (Spin version 4.0.7 - - 1 August 2003) Full statespace search for: never claim - (none specified) assertion violations + acceptance cycles - (not selected) invalid end state + State-vector 28 bytes, depth reached 7, errors: 0 14 states, stored 3 states, matched 17 transitions (= stored + matched) 0 atomic steps
Summary • State-space = very small • No errors • No assertion violations
Dekker's mutex algorithm bool turn, flag[2]; byte cnt; active [2] proctype mutex() /* Dekker's 1965 algorithm */ { pid i, j; i = _pid; j = 1 - _pid; again: flag[i] = true; do :: flag[j] -> if :: turn == j -> flag[i] = false; !(turn == j); flag[i] = true :: else fi :: else -> break od; cnt++; assert(cnt == 1); cnt--; /* critical section */ turn = j; flag[i] = false; Goto again }
Spin Verification spin -a mutex.pml cc -o pan pan.c ./pan (Spin Version 4.0.7 – 1 August 2003) + Partial Order Reduction Full statespace search for: never claim - (none specified) assertion violations + cycle checks - (disabled by -DSAFETY) invalid end states + State-vector 20 byte, depth reached 65, errors: 0 190 states, stored 173 states, matched 363 transitions (= stored+matched) 0 atomic steps hash conflicts: 0 (resolved) (max size 2^18 states)
Faulty Mutual Exclusion Algorithm Byte cnt; Byte x,y,z; Active[2] proctype user() { byte me= -pid+1; Again: x=me; if :: (y==0)||y==me)->skip :: else->goto again fi;
z=me; if :: (x==me)->skip :: else->goto again fi; y=me; if :: (z==me)->skip :: else->goto again fi; /* enter CS */ cnt++; assert(cnt==1); cnt--; goto again; }
Spin’s Verdict spin – a mutex_flaw.pml cc -0 pan pan.c .pan pan: assertion violated (cnt==1) (at depth 53) pan: wrote mutex_flaw.pml.trail
Bakery Algorithm proctype A() { do :: 1 -> turnA = 1; turnA = turnB + 1; (turnB == 0) || (turnA < turnB); mutex ++; mutex --; turnA = 0; od }
Dekker’s solution to the two processmutual exclusion problem #define true 1 #define false 0 #define Aturn false #define Bturn true bool x, y, t; proctype A() { x = true; t = Bturn; (y == false||t == Aturn); /*critical section*/ x = false } proctype B() { y = true; t = Aturn; (x == false || t == Bturn); /* critical section */ y = false } init { run A(); run B() }
Peterson's Mutual Exclusion bit active [2] ; bit last ; byte crit ; proctype threadMX (bit self) { do :: break :: active [self] = 1 ; last = self ; (last != self || ! active[1-self]) ; /* insufficient is: (last != self) ; */ crit ++ ; assert crit == 1 ; /* mutual exclusion */ crit -- ; active [self] = 0 ; od } init { run threadMX (0) ; run threadMX (1) ; }
Message Passing Mtype = {ini, ack, dreg, data, shutup, quiet, dead}; Chan M = [1] of {mtype}: Chan W = [1] of {mtype};
Active proctype Mproc() { W!ini; /*connection */ M?ack; /*handshake */ timeout-> /*wait */ if /*two options */ :: W!shutup /*start shutdown */ :: W!dreq /*or request data */ M?data /*receive data */ do :: W!data /*send data */ :: W!shutup; /*or shutdown */ break; od fi; M?shutup; /*shutdown handshake */ W!quiet; M?dead }
Active proctype Wproc() { W?ini; /*wait for ini */ M!ack; /*acknowledge */ do /*3 options */ :: W?dreq -> /*data requested */ M!data /*send data */ :: W?data -> /*receive data */ skip /*no response */ :: W?shutup -> M!shutup; /*start shutdown */ break; od; W?quiet; M!dead }
Spin –c protocol Proc 0 = Mproc Proc 1 = Wproc q\p 0 1 1 W!ini 1 - W?ini 2 - M!ack 2 M?ack Timeout 1 W!shutup 1 - W?shutup 2 - M! shutup 2 M?shutup 1 W!quiet 1 - W?quiet 2 - M!dead 2 M?dead …
SPIN Output proctype A state 1 -> state 2 => x = 1 state 2 -> state 3 => t = 1 state 3 -> state 4 => ((y == 0) || (t == 0)) state 4 -> state 5 => x = 0 state 5 -> state 0 => -end proctype B state 1 -> state 2 => y = 1 state 2 -> state 3 => t = 0 state 3 -> state 4 => ((x == 0) || (t == 1)) state 4 -> state 5 => y = 0 state 5 -> state 0 => -end proctype init state 1 -> state 2 => (run A()) state 2 -> state 3 => (run B()) state 3 -> state 0 => -end-
Reader-Writer #define invariant (nr == 0 || !busy) byte nr; bool busy; proctype reader(){ do :: atomic{ (!busy) -> nr=nr+1} /* reading */ nr=nr-1 od; } proctype writer (){ do ::atomic { (nr==0 && !busy) ->busy=1} /*writing*/ busy=0; od;
#define invariant (nr == 0 || !busy) byte nr; bool busy; proctype reader(){ do :: atomic{ (!busy) -> nr=nr+1} /* reading */ nr=nr-1 od; } proctype writer (){ do ::atomic { (nr==0 && !busy) ->busy=1} /*writing*/ busy=0; od; init{ nr=0; busy=0; run reader(); run reader(); run writer(); run writer(); } if :: (! ((invariant))) -> goto accept_all :: (1) -> goto T0_init fi; accept_all: skip } Reader/Writer