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