Cosc 4740 Chapter 6, Part 2 Process Synchronization Busy loop/waiting problem • Wastes CPU time • If scheduler takes into account priority, then low priority process (becomes CPU bound), while other may be high priority – If low priority gets the lock, while the high priority process will have to wait longer while low priority process finishes/release lock. Busy loop fix • Can use sleep() and wakeup() • sleep() blocks the process – it won’t be scheduled • wakeup() moves the sleeping process to the ready list. – If it is blocked for other reasons don’t wake it. – The wakeup() call is issued by another process. The sleeper can not wakeup itself. • Can be setup for a timed event, ie sleep for 1 second, but O/S “Wakes” the process after 1 second. Producer/Consumer or Bounded buffer problem • 2 processes communication by sharing a fix sized buffer. – (limited way): The producer puts stuff in and the consumer takes stuff out. – Producer buffer consumer • It can not just put it in or remove it randomly. The buffer can be full or empty. Must be a balance between producer and consumer. First attempt Buffer: //class An array next: //index variable – next input location // –producer interested in this last: // next output location //– consumer interested in this N: //size-fixed for buffer. Producer() while (true) { produce (item) put_info( buffer[next], item) next = (next+1) mod N } • This is a simple minded version. Assumes never full, never empty, no problems Consumer() while (true) { get_item(buffer[last], item) last = (last +1) mod n consume(item) } • Problems: • what if buffer is full or empty? – If full – don’t put_info – if empty – don’t get_item. • One solution is a busy waiting loop while empty or full. • OR use sleep() and wakeup() • int count = 0 // global –shared between the processes = # items in buffer array producer () while (true) { produce (item) if (count == N) sleep(); enter_item(item) count++ if (count == 1) wakeup(consumer) } Consumer () while (true) { if (count == 0) sleep() remove_item(item) count --; if (count == N-1) wakeup(producer) consume(item) } • problems: – Consumer codes running reads count and it is zero. Before it goes to sleep, the process is interrupted. – The producer process starts running • puts item in buffer, count = 1 and wakeup consumer. • Which would do nothing, since it was interrupted and already on the ready-queue. – Consumer switched onto the CPU and then goes to sleep. – Producer runs until buffer is full and it too goes to sleep. The Fix • In 1965 Dykstra invented semaphores to solve these problems. Quick Review • tsl instruction () – it’s an atomic instruction – allows programmer to set a lock without interruption • problem: busy/waiting wastes CPU • solution: sleep & wakeup • problem: w/ sleep & wakeup – no count of how many proc are sleeping so could put all to sleep Semaphore • Synchronization tool that does not require busy waiting • Semaphore S – integer variable – contains non-neg integers (>= 0) – Provides a way to count the number of sleep/wakeups invoked • Two standard operations modify S: wait() and signal() – Originally called P() and V() Semaphore as General Synchronization Tool • Counting semaphore – integer value can range over an unrestricted domain • Binary semaphore – integer value can range only between 0 and 1; can be simpler to implement – Also known as mutex locks • Can implement a counting semaphore S as a binary semaphore • Provides mutual exclusion Semaphore S; // wait (S); Critical Section signal (S); initialized to 1 Semaphore Implementation • Must guarantee that no two processes can execute wait() and signal() on the same semaphore at the same time – Wait() and Signal() are also critical sections for the variables S. – Note that applications may spend lots of time in critical sections and therefore busy waiting is not a good solution. Semaphore Implementation (2) • With each semaphore there is an associated waiting queue. Each entry in a waiting queue has two data items: – value (of type integer) – pointer to next record in the list • Two operations: – block – place the process invoking the operation on the appropriate waiting queue. – wakeup – remove one of processes in the waiting queue and place it in the ready queue. Semaphore Implementation (3) • Implementation of wait: wait (S){ • Implementation of signal: Signal (S){ S->value--; value++; if (value < 0) { //add this process to //waiting queue for S block(); } if (value <= 0) { //remove a process P //from the waiting queue //for S wakeup(P); } } } Deadlock and Starvation • Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes • Let S and Q be two semaphores initialized to 1 P0 P1 wait (S); wait (Q); . . . signal (S); signal (Q); wait (Q); wait (S); . . . signal (Q); signal (S); • Starvation – indefinite blocking. A process may never be removed from the semaphore queue in which it is suspended. • Priority Inversion – Scheduling problem when lower-priority process holds a lock needed by higher-priority process – Solved via priority-inheritance protocol Classical Problems • Solutions using Semaphores – Bounded-Buffer Problem • Producer-Consumer Problem – Dining-Philosophers Problem – Readers and Writers Problem Bounded-Buffer Problem • • • • N buffers, each can hold one item Semaphore mutex initialized to the value 1 Semaphore full initialized to the value 0 Semaphore empty initialized to the value N. Bounded Buffer Problem (Cont.) • The structure of the producer process while (true) { // produce an item produce(item); wait (empty); //check if buffer is full, if yes, block wait (mutex); //enter CR to add item. // add the item to the buffer signal (mutex); //leave CR system. signal (full); //increment full } Bounded Buffer Problem (2) • The structure of the consumer process while (true) { wait (full); //check to see if buffer is “full” //start w/full =0, so it blocks if there is nothing yet. wait (mutex); //Enter CR // remove an item from buffer signal (mutex); //leave CR signal (empty); //increment empty // consume the removed item } Bounded Buffer Problem (3) • The producer block should be removed when at least 1 item is removed • The consumer must tell the producer by a signal(s) operation. • Recall that semaphores keep a count of wait() & signal() calls and are atomic functions. Dining-Philosophers Problem • 5 philosophers • 5 forks • Each philosopher – (1) eat – (2) thinks • Catch: If a philosopher wants to each it needs two forks philosopher(i) while(1) { think() pick up 2 forks eat() put down forks } • Problem: – To eat, a philosopher needs 2 forks – 2 neighbors can not eat at the same time. – can not pick up more than 1 fork at a time. – So 1 philosopher might starve! With No locking. philosopher(i) left = I; right = (I+1) mod 5; while(1) { think() pick_fork[right] =1 pick_fork [left ] =1 eat () put_fork [right]=0 put_fork [ left] =0 } With Semaphores semaphore S[5] = {1,1,1,1,1} // init to 1 while (1) { think(); Wait(S[left]); Wait(S[right]); eat () Signal(s[left]); Signal(s[right]); } • Now when a fork is not avail, the philosopher will block. • Problem: – If all philosophers pick up left fork 1st, then no right forks are available. • We need to be able to pick up both forks or none, so we need a semaphore to do this: make picking up forks atomic. Philosphers(i) { while(1) { think(); take_forks(i); eat(); put_forks(i); } } put_forks(i) { Wait(mutex); Signal(S[left]); Signal(S(right]); Signal(mutex); } take_forks(i) { Wait(mutex); Wait(S[left]); Wait(S[right]); Signal(mutex); } So what problems arise? • So what problems arise? – If philosophers is blocked on a right or left fork, he will be deadlocked, because no other process can put_forks, since it is in it’s critical section! • There are other methods that can fix this problem. Readers/writers problem Two types of processes 1. readers – reads information from the database 2. writers – write to the database • And we want concurrent access to the database. • problem 1 – 2 writers try to modify the db at same time. – constraint: At must 1 writer should access the database at any time • problem 2 – a reader is attempting to read an entry while it is in the process of being modified and could read an intermediate value (or wrong value). – Constraint: A writer and reader cannot access the database at the same time. try #1 semaphore db = 1; writer () { P(db); // protect db from other writers and readers modify database V(db); } readers () P(db); read database V(db); } • no problem with constraints but it excludes other readers try #2 • we want to allow multiple readers, but no readers w/ writers v = 0; db =1; writer() reader() P(db) if (v==1) v = 1; read (database) modify(database) v = 0; V(db) • Problem: v is not protected. When you access v, it should be w/in a semaphore • Why? • Also readers can already be in the database, when the writer enters. Writer is not blocked by readers. • P(db) in reader, will also block readers too. • We want a P(db) block on writers put on the by the 1st reader only and removed by last reader! Try #3 semaphore mutex int rc =0; //reader count reader() P(mutex) // Protect rc if (rc ==0) { P(db); } rc ++; V(mutex); read database; P(mutex) rc – if (rc ==0) { V(db); } V(mutex); writer() P(db); modify database V(db); • This solution allows multi readers – no writer/reader combo, but writer will starve • may never get a chance if readers keep arriving. Writer unblocked only when all readers done. • So what is a fair solution? – We need to alternate between writers and readers. – You must maintain a count of # of readers reading and you must maintain a flag when writer arrives. – Can be done with higher level primitives to make it easier. Problems with Semaphores • Incorrect use of semaphore operations: – signal (mutex) …. wait (mutex) – wait (mutex) … wait (mutex) – Omitting of wait (mutex) or signal (mutex) (or both) • Deadlock and starvation higher level primitives • There are two primitives that well look at: • Conditional Critical Region (CCR) • Monitor Conditional Critical Region • Explicitly designates a section of the program as critical. – a mechanism to specify variables to be protected in critical region. – conditions under which a critical section may be entered designated critical section region //always assoc w/ resource variable protection resource resource R1name: v1, v2, … Vn R2name: v1, v2, …, Vn region R1name when B do S end • // B: condition of entry: boolean expression • // S: statement list • // region statements are scattered through the program. Region statements can be nested, but CAUTION: deadlocks become very likely. Example • Unisex bathroom problem – single bathroom – 2 processes types: male and female • constraints – No male & Female in BR at same time, multimale ok, multi-female ok. resource bathroom: fc=0, mc=0; //protected variables (male count = mc, etc) • //Note male and female solution is the same, expect switch mc and fc male() region bathroom when fc = 0 do mc ++; use bathroom mc --; end • Problem – The region statement at any one time is only for only one process to be in the region, so multimale (multi-female) constraints fails Try #2 males() region bathroom when fc==0 do mc++ end use bathroom region bathroom when true do mc-end • Allows concurrency. Second part, protect, so only one process accessing mc at the same time (mutual exclusion). • problem: starvation Dinning Philosophers (again) resource eat: forks[5] ={0,0,0,0,0} philosopher(i) //assume left and right are defined think(); region eat when ((fork[left] ==0)&&(fork[right]==0) do fork[left] =1; fork[right]=1; end eating(); region eat when true do fork[left] =0; fork[right]=0; end Monitors • A monitor is a collection of procedures and variables and data structures – Processes can access these variables only by invoking the procedures in the monitor – At most 1 process can be active at any time in the monitor. This provides mutual exclusion – Unlike CCR’s the code is NOT scattered. It is in the monitor • condition variables provided: 2 operations defined – wait(c): causes the process to block on C – and the processes leaves the monitor – signal(c): unblocks one of the processes. The unblocked process runs immediately. Monitors • Only one process may be active within the monitor at a time monitor monitor-name { // shared variable declarations procedure P1 (…) { …. } … procedure Pn (…) {……} Initialization code ( ….) { … } … } } Schematic view of a Monitor Condition Variables • condition x, y; • Two operations on a condition variable: – x.wait () • a process that invokes the operation is suspended. – x.signal () • resumes one of processes (if any) that invoked x.wait () Monitor with Condition Variables Condition Variables Choices • If process P invokes x.signal (), with Q in x.wait () state, what should happen next? – If Q is resumed, then P must wait • Options include – Signal and wait – P waits until Q leaves monitor or waits for another condition – Signal and continue – Q waits until P leaves the monitor or waits for another condition – Both have pros and cons – language implementer can decide – Monitors implemented in Concurrent Pascal compromise • P executing signal immediately leaves the monitor, Q is resumed – Implemented in other languages including Mesa, C#, Java Monitor m_name variable1, … procedure P1( …) statements end procedure P2(…) statements end begin // initialization code for variables end end m_name // invoked by: m_name.P1(…) Unisex (again) • Bathroom problem again but cure starvation by alternate the sexes if op. sex is waiting • int mc, fc, mcw, fcw – // mcw,fcw male/female count waiting • condition fq, mq – // mq/fq male/female queue of processes blocked procedure enter_bathroom_f( ) if (mc == 0 && mcw == 0) fc ++ else fcw ++ // waiting female count wait(fq) // blocks females fcw – // not waiting any more fc ++ // now female is in the bathroom signal(fq) // signal next female, if none, // signal is lost, but ok. procedure exit_bathroom_m( ) // like reader/writer the last male leaving must // signal to female that he has left. mc -// male left if (mc == 0 && fcw !=0) // last male out and female waiting // signal wakes up just one process. signal (fq) // no other process can interrupt this since the monitor gives exclusive use // to each procedure as each fq is executed, it runs immediately • So 2 queues are waiting to enter monitor • If only male signal process executes the signal(fq), then processes gets stuck on the queue and everything starts to block – deadlock, so we need to have female signaled to signal the next female to enter. – Also, since signal is last statement, male does not block it is finished. • Each condition has its own queue Solution to Dining Philosophers monitor DP { enum { THINKING; HUNGRY, EATING) state [5] ; condition self [5]; void pickup (int i) { state[i] = HUNGRY; test(i); if (state[i] != EATING) self [i].wait; } void putdown (int i) { state[i] = THINKING; // test left and right neighbors test((i + 4) % 5); test((i + 1) % 5); } Solution to Dining Philosophers (2) void test (int i) { if ( (state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) && (state[(i + 1) % 5] != EATING) ) { state[i] = EATING ; self[i].signal () ; } } initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING; } } Solution to Dining Philosophers (3) • Each philosopher I invokes the operations pickup() and putdown() in the following sequence: Philospher(int i) { while (1) { Think() dp.pickup (i) eat() dp.putdown (i) }} Solution to Dinning Philosophers (4) • Is Deadlock Possible? • Is Starvation possible? Q&A