Operation System Concepts Ch.6 Process Synchronization

6.1 Background

shared data, uncontrolled scheduling, inconsistency, execution order ...

Race condition: outcome depends on access order


6.2 Critical-Section Problem

critical section: one in, no other in

entry/critical/exit/remainder section

mutual exclusion, progress, bounded waiting

non-preemptive: free of race conditions in kernel mode, not responsive


6.3 Mutex Locks

declare a lock variable (e.g. mutex, holds the state of lock, either free or held)

hardware sync primitives needed

lock() to acquire the lock: if it is free, acquire it; otherwise, will not return

unlock() to free the lock: if no other waiting, change to free; otherwise, one waiting thread will acquire


6.3.1 Build a Lock

naive flag

For lock() we just code while(flag); flag=1;

For unlock() we just let flag=0;

violate Mutual exclusion and Bounded waiting

Peterson's

Assume load and store are atomic

int turn; bool flag[2];

For process i, we have

flag[i]=1, turn=j;
while(flag[j] && turn==j);
...
flag[i]=0;

Here are some naive wrong solutions:

while(flag[j]);
flag[i]=1;
...
flag[i]=0;

violate Mutual exclusion. (I1,J1,I2,J2,-_-b)

flag[i]=1;
while(flag[j]);
...
flag[i]=0;

violate Progress. (I1,J1,I2,J2,-_-bb)

turn=j;
while(turn==j);
...

violate Progress. (I1,J1,End1,I2,J2,-_-bbb)

Bakery

choosing[]={0}, number[]={<0,0>}

For process i, we have

choosing[i]=1;
number[i]=<max(number[])+1, i>;
choosing[i]=0;
for j in range(0,n):
	while(choosing[j]);
	while(number[j] && number[j]<number[i]);
...
number[i]=<0,0>;

Without choosing[], it violates Mutual exclusion

For example, Thread i calculates max(...) but haven't store to number[], we switch to Thread j and it calculates max(...) (result is same as i's, if we have choosing, then only one can enter c.s.), passes the while loop and enters the c.s., then we switch back to Thread i, it stores the result of max, and passes the loop (assuming i<j) and enters the c.s. Ahhh...

F**k it! may be a little hardware support will make it easier...

Controlling Interrupts

Just disable interrupts (ligation?)

Negatives: privileged operation, multiprocessors, lost interrupts, inefficient

Test-and-set

return old and update to new

lock() is while(TAS(flag,1)==1);

unlock() is flag=0;

violates Bounded waiting

Compare-and-swap

return old, and update to new if old=expected

lock() is while(CAS(flag,0,1)==1);

violates Bounded waiting

can achieve lock-free sync: Atomic Increment like this and no deadlock can arise (?)

// Argu: *counter, amount
do old=*counter; while CAS(counter, old, old+amount)==0;

Load-Linked & Store-Conditional

(to be continue)

Fetch-and-Add

(to be continue)

6.3.2 Spin-Waiting/Yield

(to be continue)


6.4 Locked Data Structures

(to be continue)


6.5 Condition Varibles

as an explicit queue, Conditional Varibles is always used with a lock and a flag

wait() put itself to sleep (lock holding, free the lock, and need to catch it when wake up)

signal() wake a sleeping thread waiting on this condition (lock holding)

thus we can implement join() like

lock(m);
while(done==0) wait(c,m);	// while->if is ok here
unlock(m);

also, exit() like

lock(m);
done=1;
signal(c);
unlock(m);

focus: we use it with a mutex lock m. If not, wait after signal may occur. The same without the flag done

6.5.1 Solve Producer-Consumer Problem

give the code of producer (for one time) as

lock(m);
while(count==MAX) wait(cv_empty, m);
signal(cv_fill);
unlock(m);

6.6 Semaphores

an object with an integer value

target: functionality, correctness and convenience (robust, clean, efficient)

wait() or P()

x--;
if(x<0) push(me), block();

signal() or V()

x++;
if(x<=0) wakeup(pop());

tip: the value of x can be regarded as the count of some resource available. If x>0 then some resource are free to use, while x<0 means some are waiting for the resource.

6.6.1 Basic Patterns

Signaling/Rendezvous

we have statement a and b in different thread, how to guarantee a before b?

For thread A

a;
signal(s);

For thread B

wait(s);
b;

it's evident that we need s=0; initially

rendezvous is just like signaling, skip

Mutex/Multiplex

wait(mutex);
...
signal(mutex);

initially mutex=1;

for Multiplex, just modify the initial value

Barrier/Reusable Barrier

how to ensure that no thread executes critical point until after all threads have executed renderzvous?

rendezvous;

wait(mutex);
count++;
signal(mutex);

if(count==n) signal(barrier);

wait(barrier);
signal(barrier);

critical point;

note that we can also use the following code

rendezvous;

wait(mutex);
count++;
if(count==n) signal(barrier) for n times;
signal(mutex);

wait(barrier);
critical point;

reusable barrier: just a small modification

rendezvous;

wait(mutex);
count++;
if(count==n) signal(b1) for n times;
signal(mutex);

wait(b1);
critical point;

wait(mutex);
count--;
if(count==0) signal(b2) for n times;
signal(mutex);

wait(b2);
...

Pairing

// Leader
signal(leader);
wait(follower);
dance();

// Follower
signal(follower);
wait(leader);
dance();

the code above cannot ensure dance() are executed in pairs

// Leader
wait(mutex);
if(num_follower>0) num_follower--, signal(leader);
else num_leader++, signal(mutex), wait(follower);
dance();
wait(pairing);
signal(mutex);
Follower
wait(mutex);
if(num_leader>0) num_leader--, signal(follower);
else num_follower++, signal(mutex), wait(leader);
dance();
signal(pairing);
// !!! no signal mutex !!!

6.6.2 Classical Problems

Producer-Consumer Problem

(to be continue)

Readers-Writers Problem

(to be continue)

Dining Philosophers

(to be continue)

6.6.3 Pros/Cons

Pros: robust/clean/efficient, better performance

Cons: broadcast, priority inheritance


6.7 Monitors

(to be continue)

上一篇:Synchronization


下一篇:LTE同步技术(一)