Run the program with six producers, six consumers and buffer size 5 as defined in file ProducerConsumer.h for a while and click on the History button to activate the History Graph window as shown below. It shows that six producer and six consumer threads are running initially, and all six producer threads and first two consumer threads are blocked by semaphores NotFull and NotEmpty, respectively. The last four running consumer threads are yet to reach semaphore NotEmpty.
Click on the semaphore names to bring up all semaphore windows. As you can see, all producer threads are blocked on semaphore NotFull and the first two consumer threads are blocked on semaphore NotEmpty. Since there is no thread that can make the request of accessing the buffer, semaphore BufferLock has no waiting thread.
Run the program a little longer and one might see the following snapshot of the History Graph window. It shows all important ingredients of this program. Because the buffer size is 5, semaphore NotFull permits five producer threads to pass through as indicated in the following snapshot. Once a producer thread reaches its SW, it waits on semaphore BufferLock until it is signaled by another thread indicated by a SE tag. Now, take a look at thread Producer0. Because it is the first thread that reaches semaphore BufferLock, this thread does not have to wait and hence passes a SE tag immediately. Now, it is in the buffer. After processes its data, this thread issues two signals (i.e., two SS tags), one for releasing the buffer and the other for notifying a consumer that the buffer has data. As a result, a line connects thread Producer0's SS tag and Producer1's SS tag. This allows the latter thread to enter the buffer and process its data. Note that the latter thread does not have to be Producer1 and it could be another thread that is waiting to access the buffer. The line segment connecting thread Producer0's second SS tag and Consumer0's first SE tag indicates consumer thread Consumer0 is notified about the fact that the buffer is non empty. Note also that this latter thread does not have to be Consumer0, and it can be another thread that is waiting on semaphore NotEmpty. Similar activities happen between producer thread Producer1 and consumer thread Consumer1.
Return to producer thread Producer0. After this run, it returns to the top and tries to deposit its second data item. It is then blocked. Similar happens to producer thread Producer1. Producer thread Producer2 has finished its access to the buffer and then released it. Producer threads Producer3 and Producer4 are still waiting to access the buffer, while producer thread Producer5 is blocked by semaphore NotFull.
On the other hand, consumer thread Consumer0 just passed semaphore NotEmpty and is blocked by semaphore BufferLock. Consumer thread Consumer1 is released from semaphore NotEmpty and is in the process to wait on semaphore BufferLock. All the other consumer threads are still waiting on semaphore NotEmpty. Although we derive the above information from the History Graph window, one can also get the same information by tracing the program step-by-step and from the semaphore windows:
The last event recorded in the History Graph window is a SS tag on producer thread Producer2's history bar. As mentioned earlier, this releases the buffer lock. If this program runs for just a little longer, a line segment will connect this SS tag to a SE tag on either Consumer0's, Producer4's or Producer3's history bar.