0

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?

Dr. Debasish Jana
  • 6,980
  • 4
  • 30
  • 69
  • What is the nature of the exception you anticipate? If a producer fails to write to the shared buffer then the producer should handle the exception, and the consumer should not be aware of the exception. If the consumer fails to read from the shared buffer then the consumer should handle the exception and the producer should not be aware of the exception. – Jim Rogers Dec 31 '16 at 21:55
  • @JimRogers consumeItem(item) and/or produceItem() could face an exception – Dr. Debasish Jana Jan 02 '17 at 11:30
  • That is true, but the only interface between the two threads is the buffer. If the producer encounters an exception the consumer will wait for it to recover from the exception. Similarly, if the consumer encounters an exception the producer will wait for the consumer to recover from the exception. If you worry that either the producer or consumer will not recover from an exception then you should apply a time out to each thread's wait for access to the buffer. – Jim Rogers Jan 02 '17 at 14:44
  • @JimRogers, what about set a flag stating exception occurred and do a upCount appropriately so that the other downCount does not lead to wait indefinitely? – Dr. Debasish Jana Jan 03 '17 at 08:52
  • The flag will need to provide atomic reads and writes or it will need its own mutex to prevent overlapping reading and writing. In either case you may incur a race condition between the reader and the writer. For example if the writer encounters an exception the reader can only guess at the time needed for the writer to recover from the exception, which could still result in an undefined wait for the reader. – Jim Rogers Jan 03 '17 at 14:24

1 Answers1

0

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.
Jim Rogers
  • 4,822
  • 1
  • 11
  • 24