!!!Chapter 6 Synchronization

来源:互联网 发布:ios6安装ios7软件 编辑:程序博客网 时间:2024/06/15 12:02

Cooperating processes can either directly share a logical address space(multithread) or be allowed to share data only through files or messages.

Concurrent access to shared data may result in data inconsistency.

6.1 Background

There are two ways to create shared memory between processes:

Method 1: in == out means the buffer is empty, (in + 1) % BUFFER_SIZE == out means buffer is full

BUFFER_SIZE - 1 elements are available

#define BUFFER_SIZE 10typedef struct {    ...}item;item buffer[BUFFER_SIZE];int in = 0;int out = 0;// producer processitem nextProduced;while(true) {    while(((in+1)%BUFFER_SIZE) == out)        ;    // buffer is full, do nothing    buffer[in] = nextProduced;    in = (in + 1)%BUFFER_SIZE;}// consumer processitem nextConsumed;while(true) {    while(in == out)        ;    // buffer is empty, do nothing    nextConsumed = buffer[out];    out = (out + 1)%BUFFER_SIZE;}

Method 2: use an integer variable counter.

BUFFER_SIZE elements are available

// producerwhile(true) {    while(counter == BUFFER_SIZE)        ;    // buffer is full, do nothing    buffer[in] = nextProduced;    in = (in + 1)%BUFFER_SIZE;    counter++;}// consumerwhile(true) {    while(counter == 0)        ;    // buffer is empty, do nothing    nextConsumed = buffer[out];    out = (out + 1)%BUFFER_SIZE;    counter--;}
When we do produce/consume at the same time, the counter value may be incorrect. P 227.

When several processes access and manipulate the same data concurrently and the outcome of the execution depends on the particular order in which the access takes place, it's called race condition.

6.2 The Critical-Section Problem

Each process has a segment of code, called critical section, in which the process may be changing common variables, updating a table, writing a file, and so on.

When one process is executing in its critical section, no other process is to be allowed to execute in its critical section. No two processes are executing in their critical sections at the same time.

The critical-section problem is to design a protocol that the processes can use to cooperate.

Each process must request permission to enter its critical section, the code implementing the request is entry section. Entry section may be followed by anexit section. All other codes areremainder section.

A solution to the critical-section problem must satisfy following requirements:

1. Mutual exclusion. If process P1 is executing in its critical section, then no other processes can be executing in their critical sections.

2. Progress. If no process is executing in its critical section and some processes wish to enter their critical sections, then only those processes that are not executing in their remainder sections can participate in deciding which will enter its critical section next, and the selection cannot be postponed indefinitely.

3. Bounded waiting. There exists a bound, on the number of times that other processes are allowed to enter their critical sections after a process has made a request and before that request is granted.

6.3 Peterson's Solution

Peterson's solution is a classic software-based solution to the critical-section problem.

Peterson's solution is restricted to two processes that alternate execution between their critical sections and remainder sections.

Peterson's solution requires two processes to share two data items:

int turn;          // 0 and 1 means the two processesboolean flag[2];   // flag means if process 0/1 is ready to enter critical section
Implementation of Peterson's solution:

// Structure of process Pi in Peterson's solutiondo {    flag[i] = TRUE;    turn = j;    // Pi will waiting if j is ready and turn is on j    while (flag[j] && turn == j);        critical section    flag[i] = FALSE;        remainder section} while(TRUE);

6.4 Synchronization Hardware

Software-based solutions such as Peterson's are not guaranteed to work on modern computer architectures.

Any solution to the critical-section problem requires a simple tool --- a lock.

Race conditions are prevented by requiring that critical regions be protected by locks. That is, a process must acquire a lock before entering a critical section; it releases the lock when it exits the critical section.

do {        acquire lock            critical section        release lock            remainder section} while(TRUE);
The critical-section problem could be solved simply in a uniprocessor environment if we could prevent interrupts from occurring while a shared variable was being modified.

Modern computer systems therefore provide special hardware instructions that allow us either to test and modify the content of a word or to swap the contents of two wordsatomically -- that is as one uninterruptable unit.

TestAndSet() instruction:

boolean TestAndSet(boolean *target){    boolean rv = *target;    *target = TRUE;    return rv;}
TestAndSet() can ensure we write to and get the old value atomically. This instruction need hardware support.

wiki:atomicity requires explicit hardware support and hence can't be implemented as a simple function.

Mutual-exclusion Implementation with TestAndSet():

// do not meet bounded waiting do{    while(TestAndSet(&lock))        ;    // do nothing        // critical section    lock = FALSE;            // remainder section} while(TRUE);

Swap() instruction:

void Swap(boolean *a, boolean *b){    boolean tmp = *a;    *a = *b;    *b = tmp;}
Mutual-exclusion Implementation with Swap()

// do not meet bounded-waiting requirementdo {    key = TRUE;    while (key == TRUE)        Swap(&lock, &key);        // critical section    lock = FALSE;        // remainder section} while(TRUE);
Algorithm that satisfy bounded-waiting requirement:

boolean waiting[n];  // init to falseboolean lock;        // init to falsedo{    // waiting[i] = TRUE, means Pi want to enter critical section    waiting[i] = TRUE;    key = TRUE;    while(waiting[i] && key)        key = TestAndSet(&lock);    waiting[i] = FALSE;            // critical section        j = (i + 1) % n;    // waiting[j] == FALSE means Pj is not asking to enter critical section,    // so we can keep increase j until we find Pj that want to enter critical section    while((j != i) && !waiting[j])        j = (j + 1)%n;        if(j == i)        lock = FALSE;          // In this case, only Pi want to enter, so we reset lock    else        waiting[j] = FALSE;    // In this case, another process want to enter, so we set w[j]            // remainder section} while(TRUE);
Prove:

1. At the beginning, only the first process(Pi) will get lock = FALSE and enter critical section.

2. Once the Pi finish critical section, it will find the next process that want to enter critical section and set lock or w[j] to FALSE.

So each process will wait at most n - 1 turns.

6.5 Semaphores

A semaphore S is an integer variable that, apart from initialization, is accessed only through two standard atomic operations: wait() and signal().

Definition of wait():

wait(S){    while S <= 0        ;    S--;}
Definition of signal();

signal(S) {    S++;}
All modifications to the integer value of semaphore in the wait() and signal() operations must be executed indivisibly.

6.5.1 Usage

counting semaphore: range over an unrestricted domain

binary semaphore: range only between 0 and 1

On some systems, binary semaphore are known as mutex locks, as they are locks that provide mutual exclusion.

Mutual-exclusion implementation with semaphore:

do {    wait(mutex);        // critical section    signal(mutex);        // remainder section} while(TRUE);

6.5.2 Implementation

The main disadvantage of the semaphore definition is that it requires busy waiting.

While a process is in its critical section, any other process that tries to enter its critical section must loop continuously in the entry code.

To overcome busy waiting, we can implement block(): it will send process to waiting queue; and wakeup(): it will send process back to ready queue.P 236

typedef struct {    int value;           // initial value is 1    struct process *list;} semaphore;
wait() implementation:

wait(semaphore *S) {    S->value--;        if(S->value < 0) {        add this process to S->list;        block();    }}
signal() implementation:

signal(semaphore *S) {    S->value++;    if (S->value <= 0) {        remove a process P from S->list;   // P is not the process that execute signal!        wakeup(P);    }}

6.5.3 Deadlocks and Starvation

The implementation of a semaphore with a waiting queue may result in deadlock.

When P0 execute wait(Q), it must wait until P1 executes signal(Q). When P1 execute wait(S), it must wait until P0 executes signal(S).

Another problem related to deadlock is indefinite blocking, orstarvation. Indefinite blocking may occur if we remove processes from the list associated with a semaphore in LIFO.

6.6 Classic Problems of Synchronization

6.6.1 The Bounded-Buffer Problem

To solve bounded-buffer problem, we use mutex semaphore to provide mutual exclusion for access to the buffer pool, it's initialized to 1. The empty and full semaphore count the number of empty and full buffers:

// The structure of the producer processdo {    ...    // produce an item in nextp    ....    wait(empty);      // empty is initialized to n, means n available buffers    wait(mutex);      // --mutex, so other processes will be blocked    ...    // add nextp to buffer    ...    signal(mutex);    signal(full);     // full is initialized to 0, means no message} while(TRUE)
// The structure of consumer processdo {    wait(full);    wait(mutex);    ...    // remove an item from buffer to nextc    ...    signal(mutex);    signal(empty);    ...    // consume the item in nextc    ...} while(TRUE)

6.6.2 The Readers-Writers Problem

One database is accessible from several processes. Processes only read data arereaders, Processes need read and write arewriters.

readers-writers problem: we need to ensure writers have exclusive access to the shared database while writing to the database.
first readers-writers problem: no reader be kept waiting unless a writer has already obtained permission to use the shared object

second readers - writers problem: once a writer is ready, that writer performs its write as soon as possible.

The first problem may cause writer starvation, the second one may cause reader starvation.

Solution to first readers-writers problem:

readers share a data structure

semaphore mutex, wrt;        // initialize to 1int readcount;               // init to 0
Structure of writer process:

do {    wait(wrt);    ...    // writing is performed    ...    signal(wrt);} while(TRUE);
Structure of reader process:

do {    wait(mutex);           // mutex ensure mutual exclusion of update readcount    readcount++;    if (readcount == 1)   // the first reader        wait(wrt);    signal(mutex);    ...    // reading is performed    ...    wait(mutex);    readcount--;    if(readcount == 0)        signal(wrt);    signal(mutex);} while(TRUE);
When a writer executes signal(wrt), we may resume the execution of either the waiting readers or a single waiting writer. The selection is made by the scheduler.

The readers-writers problem and its solutions have been generalized to providereader-writer locks,the lock is used:

1. In application where it is easy to identify reader/writer

2. In application that have more readers than writers. (The time multiple readers work should compensate the time to create lock)

6.6.3 The Dining-Philosophers Problem

Five philosophers are sitting around a table, there are five chopsticks. One must get two chopsticks to eat.

First, we need to create an array of semaphore:

semaphore chopstick[5];    // init to 1
The structure of philosopher i:

do {    wait(chopstick[i]);    wait(chopstick[(i + 1)%5]);    ...    // eat    ...    signal(chopstick[i]);    signal(chopstick[(i + 1)%5]);    ...    // think} while(TRUE);
To avoid deadlock:
1. Allow at most four philosophers to be sitting simultaneously at the table.

2. Allow a philosopher to pick up her chopsticks only if both chopsticks are available (to do this, she must pick them up in a critical section).
3. Use an asymmetric solution; that is, an odd philosopher picks up first her left chopstick and then her right chopstick, whereas an even philosopher picks up her right chopstick and then her left chopstick.

We can give different priority to prevent starvation!











原创粉丝点击