610 likes | 625 Views
This chapter discusses the problem of busy loop waiting in process synchronization and introduces the use of sleep() and wakeup() functions. It also explores the producer/consumer or bounded buffer problem and introduces the concept of semaphores as a synchronization tool.
E N D
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.
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) } First attempt
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() andV()
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; // initialized to 1 wait (S); Critical Section signal (S);
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.
Implementation of wait: wait (S){ S->value--; if (value < 0) { //add this process to //waiting queue for S block(); } } Implementation of signal: Signal (S){ value++; if (value <= 0) { //remove a process P //from the waiting queue //for S wakeup(P); } } Semaphore Implementation (3)
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 P0P1 wait (S); wait (Q); wait (Q); wait (S); . . . . . . signal (S); signal (Q); 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) { take_forks(i) { Wait(mutex); while(1) { Wait(S[left]); think(); Wait(S[right]); take_forks(i); Signal(mutex); eat(); } put_forks(i); } } put_forks(i) { Wait(mutex); Signal(S[left]); Signal(S(right]); So what problems arise? Signal(mutex); }
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 • readers – reads information from the database • 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!
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); Try #3
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, multi-male 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 multi-male (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 ( ….) { … } … } }
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 ()