CPS 356 Lecture notes: Synchronization



Coverage: [OSCJ8] Chapter 6, §§6.1-6.4 (pp. 241-249); [OSC8] Chapter 6, §§6.1-6.4 (pp. 225-234)


Sources of concurrency

  • multiple processes
  • one process with multiple threads
  • multiple physical processors
  • network


The critical section problem

Given two more more processes (or threads) which share a resource (e.g., variable or device), we must often synchronize their activity.

Must satisfy to one degree or another the concepts of mutual exclusion, progress, and bounded waiting.

Example: consider only two processes

critical section (<cs>): instructions which access shared resource

We must establish mutual exclusion: no two processes can be in their <cs> at the same time.

process producer {

   while (true) {
      while (count == BUFFER_SIZE);
      ++count;
      buffer[in] = item;
      in = (in + 1) % BUFFER_SIZE;
   }
}
process consumer {

    while (true) {
       while (count == 0);
       --count;
       item = buffer[out];
       out = (out - 1) % BUFFER_SIZE;
    }
}

Assume count = 5 and both producer and consumer execute the statements ++count and --count.

Results? count could be set to 4, 5, or 6 (but only 5 is correct).

reg_1 = count
reg_1 = reg_1 + 1
count = reg_1

reg_2 = count
reg_2 = reg_2 - 1
count = reg_2

principle of atomicity

t_0: producer executes reg_1 = count     [reg_1 = 5]
t_1: producer executes reg_1 = reg_1 + 1 [reg_1 = 6]
t_2: consumer executes reg_2 = count     [reg_2 = 5]
t_3: consumer executes reg_2 = reg_2 - 1 [reg_2 = 4]
t_4: producer executes count = reg_1     [count = 6]
t_5: consumer executes count = reg_2     [count = 4]

race condition: a situation where multiple processes access and manipulate the same data concurrently and the outcome of the execution depends on the order in which the instructions execute.

Operating system kernel code itself can have race conditions.

preemptive vs. nonpreemptive kernels

This is called the critical section problem.


A solution must satisfy three requirements:

  • mutual exclusion: only one process may execute its critical section at once.
  • progress: if no process is executing in its critical section and some processes wish to enter their critical sections, then only those processes not executing in their remainder sections can participate in the decision on which process will enter its critical section next, and this decision cannot be postponed indefinitely.
  • bounded waiting: this is a limit on the number of times other processes are allowed to enter their critical section after a process has made a request to enter its critical section and before that request is granted.


Basic idea in synchronization: need locks in one form or another

    while (true) {
       // entry section; acquire lock
       // critical section
       // exit section; release lock
       // remainder section
    }
    


Three primitive solutions to the critical section problem

  • disable interrupts during execution of <cs>
          process p_i {
             while (true) {
                // disable interrupts (a system call)
                // critical section
                // enable interrupts (a system call)
                // remainder section
             }
          }
    
    • degrades efficiency
    • not possible multiprocessor systems
  • hardware instructions (e.g., test-and-set and swap)
  • Peterson's solution


Hardware instructions

two new instructions, both versions of a read-modify-write instruction

  • test and set
       boolean test-and-set (boolean* target) {
          boolean temp = *target;
          *target = true;
          return temp;
       }
    
       boolean occupied = false;
    
       while (true) {
    
          while (test-and-set (&occupied));
    
          // critical section
          
          occupied = false;
    
         // remainder section
       }
    
    problems? starvation

  • swap
       void swap (boolean* x, boolean *y) {
          boolean temp = *x;
          *x = *y;
          *y = temp;
       }
    
       boolean occupied = false;
       boolean p_i_must_wait = true;
    
       while (true) {
       
          do
             swap (&p_i_must_wait, &occupied);
          while (p_i_must_wait);
    
          // critical section
          
          p_i_must_wait = true;
          occupied = false;
    
         // remainder section
       }
    


Peterson's Solution

shared data:

int turn;
boolean flag[2];

process p_i {

   while (true) {
      flag[i] = true; // I'm ready to enter my <cs>
      turn = j;       // but I give p_j priority

      // as long as p_j wants access and it is p_j's turn, I busy-wait
      while (flag[j] && turn == j);
   
      // critical section

      // I am no longer in my <cs>
      flag[i] = false;

      // remainder section;
   }
}

process p_j {

   while (true) {
      flag[j] = true; // I'm ready to enter my <cs>
      turn = i;       // but I give p_i priority

      // as long as p_i wants access and it is p_i's turn, I busy-wait
      while (flag[i] && turn == i);
   
      // critical section

      // I am no longer in my <cs>
      flag[j] = false;

      // remainder section;
   }
}

Peterson's solution guarantees mutual exclusion, progress, and bounded waiting.

What is the problem with Peterson's solution?

busy waiting: the waiting process wastes CPU cycles; in a uniprocessor system, the process waits until its quantum expires


High-level synchronization solutions

(which rely on primitive solutions)
  • semaphores
  • monitors


Types of solutions (low-level vs. high-level)

  • low-level
    • disable interrupts (will not work in a multiprocessor system)
    • hardware instructions
      • test and set instruction
      • swap instruction
  • high-level
    • semaphores
    • monitors (and condition variables)


Types of solutions (hardware vs. software)

  • hardware
    • disable interrupts (will not work in a multiprocessor system)
    • hardware instructions
      • test and set instruction
      • swap instruction
  • software
    • Peterson's solution
    • semaphores
    • monitors (and condition variables)


References

    [OSC8] A. Silberschatz, P.B. Galvin, and G. Gagne. Operating Systems Concepts. John Wiley and Sons, Inc., Eighth edition, 2009.
    [OSCJ] A. Silberschatz, P.B. Galvin, and G. Gagne. Operating Systems Concepts with Java. John Wiley and Sons, Inc., Eighth edition, 2010.

Return Home