Producer Consumer Problem

Problem

Producer - consumer problem is a typical textbook problem in multithreading. Here there are two groups of threads/tasks. The first group of thread/task produces something, and second group of thread/task consumes what has been produced.

Generally, the producer queues what it produces. And the consumer dequeues from the queue and consumes the produce. There is a shared resource that the producers and consumers use - the queue. A mutex could be used to protect this share resource.

Mutex solution for producer - consumer problem

  • The producer can lock the mutex and add the produce to the queue.

  • The consumer can lock the queue and check the queue

    • if the queue is empty, the consumer can unlock the queue and go to sleep for a said period of time

    • if the queue is not empty, the consumer can dequeue an element and then unlock the mutex, and then process the element

Issues in mutex solution

  • When there are few elements in the queue, the consumer will be blocked by the producer locking the mutex and vice versa.

The consumer needs to be polling the queue at regular intervals, this polling would be waste of computational resources

Semaphore to rescue

Working of semaphore

  • Semaphore is another thread/task synchronization construct similar to mutex

  • Semaphore's can be released by threads. When a semaphore is released, its count increases.

  • Semaphore can be waited upon.

    • When waited if the count is greater than 0, the count is decremented

    • When waited if the count is equal to zero, the thread calling the wait is blocked until another thread releases the semaphore.

  • Semaphores can be released multiple times consecutively

Using semaphore for producer consumer

  • The producer after producing adds the element to the queue and releases the semaphore

  • The consumer waits on the semaphore and then reads the queue

  • In this solution, the consumer will never need to poll the queue. So there would be pretty much no waste of computational resources.

Producer - Consumer implementation with Semaphore

  • The producer produces number 0 to 99

  • The consumer consumes the numbers and prints it to the console.

var resource = new Resource();
var p = Task.Run(() => Produce(resource));
var c = Task.Run(() => Consume(resource));
Task.WaitAll(p, c);


void Produce(Resource r)
{
    for (int i = 0; i < 100; i++)
    {
        r.Add(i);
    Console.WriteLine($">>>>> Produced {i}");
    }
}

void Consume(Resource r)
{
    for (int i = 0; i < 100; i++)
    {
        var x = r.Get();
        Console.WriteLine($"Consumed <<<<< {x}");
    }
}



public class Resource
{
    private SemaphoreSlim elementSemaphore = new SemaphoreSlim(0);

    private Queue<int> _queue = new Queue<int>();

    public void Add(int number)
    {
        _queue.Enqueue(number);
        elementSemaphore.Release();
    }

    public int Get()
    {
        elementSemaphore.Wait();
        var element = _queue.Dequeue();
        return element;
    }
}

Output

Conclusion

  • We have seen how a mutex could solve producer-consumer problem

  • We have analyzed the inefficiencies with mutex for producer consumer problem

  • We have then seen how semaphore elegantly solves the producer consumer problem