C++ Producer-Consumer with exception handling to prevent deadlock

436 Views Asked by At

In classical producer-consumer problem, we have producers wait when the shared buffer is full and consumers wait when the shared buffer is empty. We have two posix threads, one producer and other worker, synchroinizing with one another using sem_wait and sem_post for the empty semaphore and full semaphore (both counting semaphore). Thus, the semaphore implementation of the producer-consumer code looks like as below:

procedure producer() {
    while (true) {
        item = produceItem();
        down(emptyCount);
            down(buffer_mutex);
                putItemIntoBuffer(item);
            up(buffer_mutex);
        up(fillCount);
    }
}

procedure consumer() {
    while (true) {
        down(fillCount);
            down(buffer_mutex);
                item = removeItemFromBuffer();
            up(buffer_mutex);
        up(emptyCount);
        consumeItem(item);
    }
}

Presuming producer() and consumer() are running in independent concurrent threads, what happens when produceItem or consumeItem faces a run-time exception causing the thread to handle the exception gracefully so that both the threads can gracefully come out? Where to put the try-catch to handle the situation well?

1

There are 1 best solutions below

0
On

I do most of my multithreading using Ada. The following Ada example shows how applying a timeout to the wait for a condition variable allows the consumer to handle a non-responsive producer, and the producer to handle a non-responsive consumer.

------------------------------------------------------------------
-- Producer-Consumer Package --
------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;

procedure Protected_Producer_Consumer is
   protected Buffer is
      entry Put(Item : in Integer);
      entry Get(Item : out Integer);
   private
      Value : Integer := Integer'First;
      Is_New : Boolean := False;
   end Buffer;

   protected body Buffer is
      entry Put(Item : in Integer) when not Is_New is
      begin
         Value := Item;
         Is_New := True;
      end Put;
      entry Get(Item : out Integer) when Is_New is
      begin
         Item := Value;
         Is_New := False;
      end Get;
   end Buffer;

   task producer;
   task body producer is
      Wait_Limit : constant Natural := 5;
      Written : Boolean := False;
   begin
      for value in 1..15 loop
         Written := False;
         for try in 1..Wait_Limit loop
            Select
               Buffer.Put(Value);
               Written := True;
            or
               delay 0.5;
            end select;
            exit when Written;
         end loop;
         if not Written then
            Put_Line("Producer terminating. Consumer not responding.");
            exit;
         end if;
      end loop;
   end producer;

   task consumer;
   task body consumer is
      Wait_Limit : Natural := 5;
      Value_Read : Boolean;
      The_Value  : Integer;
   begin
      Loop
         Value_Read := False;
         for try in 1..Wait_Limit loop
            select
               Buffer.Get(The_Value);
               Value_Read := True;
               Put_Line("Consumer read value: " & Integer'Image(The_Value));
            or
               delay 0.5;
            end select;
            exit when Value_Read;
         end loop;
         if not Value_Read then
            Put_Line("Consumer terminating. Producer not responding.");
            exit;
         end if;
      end loop;
   end Consumer;

begin
   null;  
end Protected_Producer_Consumer;

Ada protected objects, such as Buffer in the example above, provide automatic mutual exclusion. In the example above the Buffer object has two entries, Put and Get. The Put entry can only be executed when the Buffer internal variable Is_New is False. The Get entry can only be executed when the Buffer internal variable Is_New is True.

The Producer task (similar to a thread in C++) contains an outer loop that sets the variable "value" to first 1, then 2, and so on up to 15. The inner loop tries to Put the value in the Buffer up to Wait_Limit times. Each time the Producer sets a timer for one half second, and then tries again if not successful. If the producer fails Wait_Limit times it writes an error message and terminates.

The consumer behavior is similar to the producer. It reads values from Buffer by calling the Get entry. It also waits one half second for each try, and terminates after Wait_Limit successive failures to read a value from Buffer.

The output of this program is:

Consumer read value:  1
Consumer read value:  2
Consumer read value:  3
Consumer read value:  4
Consumer read value:  5
Consumer read value:  6
Consumer read value:  7
Consumer read value:  8
Consumer read value:  9
Consumer read value:  10
Consumer read value:  11
Consumer read value:  12
Consumer read value:  13
Consumer read value:  14
Consumer read value:  15
Consumer terminating. Producer not responding.