Operating system 6 - producer consumer issues

Posted by jfeather on Wed, 22 Sep 2021 20:23:59 +0200

Problem Description:

  • One or more Producers place data in a buffer after production
  • A single consumer fetches data from the buffer for processing
  • Only one producer or consumer can access the buffer at any time

Using semaphores to solve the producer consumer problem

Problem analysis:

  • Only one thread can operate the buffer at any time (mutually exclusive access)

  • When the buffer is empty, the consumer must wait for the producer (conditional synchronization)

  • When the buffer is full, the producer must wait for the consumer (conditional synchronization)

resolvent:

  • Binary semaphore mutex (mutually exclusive access)
  • Resource semaphore fullBuffers (conditional synchronization)
  • Resource semaphore emptyBuffers (conditional synchronization)

Set buffer

Class BoundedBuffer{
  mutex = new Semaphore(1);
  fullBuffers = new Semaphore(0); // The initial buffer pool is empty, and fullBuffers is equivalent to the current number of resources
  emptyBuffers = new Semaphore(n); // The initial buffer is empty, and emptyBuffers is equivalent to an idle location
}
  1. We first implement the mutually exclusive access buffer

producer

ADD(Resource r){
  mutex -> p();
  Add r to the buffer;
  mutex -> v();
}

consumer

Remove(Resource r){
  mutex -> p();
  remove r from buffer;
  mutex -> v();
}
  1. Solve the problem of empty buffer

producer

ADD(Resource r){
  emptyBuffer -> p();
  p();
  Add r to the buffer;
  v();
}

consumer

Remove(Resource r){
  mutex -> p();
  remove r from buffer;
  mutex -> v();
  emptyBuffer -> v();
}
  1. Solve the problem of buffer full

producer

ADD(Resource r){
  emptyBuffers -> p();       //  There's a seat in the buffer. No, I want to occupy an empty seat
  p();											//   Is anyone in the buffer
  Add r to the buffer;
  v();										  //   My production work is finished and others can come in
  fullBuffers -> v();			  //	 There is one more resource in it
}

consumer

Remove(Resource r){
  fullBuffers -> p();		// There's something in the buffer. No, I want one
  mutex -> p(); 			 // Is anyone in the buffer
  remove r from buffer;
  mutex -> v();				// My consumption work is finished and others can come in
  emptyBuffer -> v(); // One of the resources is missing
}

Semaphore disadvantages:

The semaphore mechanism also has some disadvantages, that is, it is difficult to really use, because when the more complex synchronization problems and conditions we need to solve, the more semaphores we need. We need to handle the processing sequence between semaphores more carefully, otherwise it is easy to cause deadlock.

Solving the producer consumer problem with pipe process

Tube to improve some troubles in the critical area of semaphore processing.

The p() v() operation of semaphores is dispersed in two different processes: consumers and producers. The pairing of pv operation is more troublesome and difficult.

Pipeline: centralize the paired pv operations to simplify the synchronization control between threads

  • At most one thread executes the pipe code at any one time
  • (the main difference from the critical area) the thread in the process can temporarily give up the mutually exclusive access of the process and wait for the recovery when the event occurs.

The thread in the critical area must exit the critical area before it is possible to give up the mutually exclusive access to the critical area.

How does the pipeline implement this waiting mechanism?

  1. The thread entering the pipe enters the waiting state because the resource is occupied
  2. Each condition variable represents a waiting reason and corresponds to a waiting queue

The process also has two operations on threads

wait()

  • The thread blocks itself into the wait queue
  • Release mutually exclusive access of the pipe

signal()

If the process calling the process finds that the X condition has changed, it calls x.signal to restart a process blocked or suspended due to the X condition

Threads entering the process may be blocked and suspended in the waiting queue due to some resource constraints, so that they can not exit the process and occupy public resources. The condition variable classifies queues.

Note: threads in the entry waiting queue are threads whose conditions are unknown or have met conditions. They wait for a new round of execution of the process, and threads in the waiting queue of condition variables can return to the entry waiting queue only after the conditions are met.

The circle resource is empty, and the circle consumer enters the waiting queue.

Star resources are full, and star producers enter the waiting queue.

Conditional variable implementation

Class condition{
  int numWaiting = 0;
  WaitQueue queue;
}
Condition::Wait(lock){
  numWaiting++;  // The number of threads that call this conditional variable is increased by 1.
  Add this thread t to queue; // Block this thread into the waiting queue
  release(lock); // Release mutually exclusive access of the pipe
  schedule();   //  cpu switches to other threads need lock (unary semaphore)
  require(lock);  // When switching back, request the access permission of the management process
}
Condition::Signal(){
  if(numWaiting > 0){
    remove a thread t from queue;
    wakeup(t);  // Need lock (unary semaphore)
    numWaiting--;
  }
}

Solving the producer consumer problem with pipe process

classBoundedBuffer{
  Lock lock;
  int count = 0;  // Number of resources
  Condition notFull,notEmpty;
}

producer

BoundedBuffer::Add(c){
  lock->Acquire();  // Pipe access application
  while(count == n){ // Resources are full
	notFull.Wait(lock); // After entering the waiting queue, the thread will be switched in the wait
  }
  Add c to the buffer;
  count++;
  notEmpty.signal(); // Wake up the thread in the waiting queue
  lock->Release(); // Tube side mutual exclusion release
}

consumer

BoundedBuffer::Remove(c){
   lock->Acquire();  // Pipe access application
   while(count ==0){
     notEmpty.Wait(lock);
   } 
   Remove c from buffer;
   count--;
   notFull.Signal(); // Wake up the thread in the waiting queue
   lock->Release(); // Tube side mutual exclusion release
}

Compared with the semaphore model, the pipe model is easy to use. It can concentrate the operations of multiple synchronization conditions in one module, so as to simplify the difficulty of realizing the synchronization mechanism

Question: if the process T1 is blocked due to the X condition, when the process T2 performs the x.signal operation to wake up T1, the processes T1 and T2 are in the process at the same time. This is not allowed. How to determine which execution and which wait?

This problem is a choice problem, which can be handled in one of the following two ways:

  • T1 waits until T2 leaves the tube side or waits for another condition (Hansen tube side)
  • T2 waits until T1 leaves the tube side or waits for another condition (Hoare tube side)

Reasonably speaking, Hoare's method is better, but there is an additional overhead of thread switching, so Hansen's method is still adopted in reality. It is more efficient.

Learning materials: https://www.bilibili.com/video/BV1uW411f72n Operating system principles

Topics: Java RabbitMQ Operating System