So welcome back, guys.
Now, in this lecture video, we will going to implement the solution of our producer consumer problem
statement that we have discussed in the previous lecture videos.
So I hope you have given enough trials regarding implementing these producer and consumer flowcharts.
So in this lecture video, let us implement these producer and consumer flowcharts step by step.
So we will start with implementation of consumer Thread.
Flowchart Right.
So what I will going to do is that on the screen you can see that I am in the file assignment producer
Consumer on solutions, right?
This is the file which implements the solution.
And first of all, we will going to provide the implementation of this consumer function.
Right?
Remember, this consumer function would going to be executed by two consumer threads concurrently.
Right?
Similarly, this producer function will be executed by two producer threads concurrently.
So we will do no new thing.
We will just stick to this flowchart and we will going to implement the steps as described in this flowchart.
Right?
So revise the problem statement because we will going to implement the problem statement such that the
constraints which are described by the problem statement must not get violated in our solution.
Right.
So consumer thread starts with a step S1 That is the consumer thread.
First of all, locks the queue while the consumer thread locks the queue.
In fact, any thread which inspect the state of the data structure or make changes to the state of the
data structure, that particular thread must first of all get an explicit lock on that data structure,
right?
So step S1 is all about locking the mutex of the queue, right?
So starting our implementation of the consumer function, you can see that in the step S1 I'm simply
locking the queue, right?
So this is the step S1.
Now, since in the step S1, the consumer thread has already logged the data structure.
Now consumer thread is in a position to inspect the state of the data structure, right?
Since the data structure has been logged in step S1 So it simply means that during the time when the
consumer thread is inspecting the state of the data structure, no other thread can change the state
of the data structure, right?
This is what the principle of mutual exclusion says, right?
So in step number S2, the consumer thread is checking whether the queue is empty or not.
If the queue is empty, then consumer thread really do not have anything to consume and consumer thread
will block itself in step number S3 Right.
Whereas.
If this queue is not empty right then our consumer thread will check whether the queue is full and if
the queue is not full then we need to crash our program.
Right.
If you remember one of the constraint of our producer consumer problem statement was that that the consumer
must get an access to the queue only when the queue is full.
The consumer thread must not get an access to the queue, which is half filled.
Right.
So if the consumer thread gets an access to the queue and if the queue is not full, then it means that
we have violated the constraint of our problem statement.
And this is something that we do not want our program to land in such a situation.
Therefore, we will going to crash our program intentionally.
Right?
However, in case if the queue is full, then our consumer thread now is in a position to actually start
consuming the elements from this queue one by one until the queue is empty.
So this is the step number four, right?
So let us implement the step number S2, S3 and S4.
So you can see here that our consumer thread has already got a lock on the queue.
And now in step number S2, we are going to check whether the queue is empty or not.
So here we are checking the predicate condition that if the queue is empty right then it means that
our consumer thread really has nothing to do and our consumer thread simply block itself.
It will block itself by invoking the api p thread condition.
Wait.
Right.
And you need to pass the condition variable of the queue as well as the mutex of the queue.
Right.
So immediately after executing the step number three, this particular mutex which is logged in step
number s one would be unlocked automatically.
So the consumer thread will block at the step number s three if it finds the queue already empty and
our consumer thread will proceed or resume its execution beyond line number 91 only when it receives
a signal from some other thread of the program.
In that case, our consumer thread would be woken up.
Right.
And it should check the predicate condition again.
Right.
This is what we have already discussed.
That checking of the predicate condition must be done in a while loop.
Right?
So this logic implements the step number S2 and step number S3.
And once our consumer thread comes out of this while loop, it simply means that the queue is no more
empty.
Right?
So now it's time for our consumer thread to check if the queue is full.
And here you can see that we are simply crashing our program if the queue is not full.
Right.
We have a constraint in our problem statement that consumer must start consuming the elements from the
full queue only.
Right.
So here we have implemented this assert box in the flowchart.
And going further now the consumer thread will resume its execution beyond the line number 96.
Only when the queue is full.
Right?
It means now it's time for us to implement the step number.
S4 Right.
So in step number S4, what I will going to do is that our consumer thread would going to consume an
element from the queue one by one within a while loop until the queue becomes empty.
Right.
So you can see that our consumer thread is iterating over the queue and every time it is removing one
element from the queue and we are simply printing that an element has been removed or consumed from
the queue.
And we are also printing which element has been removed from the queue.
Right.
Printing this information would give you a useful insight that what actually your program is doing and
in case if your program encounters certain deadlocks or erroneous output, these logs would help you
to root cause the problem and fix it right?
So you can see that the line numbers from 100 to 104 implements this particular box that is consume
all the elements of the queue until the queue is empty.
And as soon as the queue becomes empty, our consumer thread will go beyond this while loop and as per
the flowchart we need to send.
Signal, right?
We need to send a signal on the condition variable of the resource.
And finally, the step number s five says simply unlocks the queue.
So here I will going to unlock the mutex on the queue because the consumer thread has completed its
operation on the queue.
Right.
So you can see that the consumer thread after unlocking the mutex on the queue.
The consumer thread exits because it has completed its operation, which it was supposed to perform
on the queue.
Right.
So you can see that I have successfully implemented this complete flowchart as a consumer function.
Right thing here to note is that that we always check the state of the resource within a while loop.
This is you can say that this is one of the rule of the thread synchronization, right?
And one of the question that must be crossing your mind is that that why are we using p thread condition
broadcast and not p thread condition signal?
Well, the answer to this question you will get while doing the assignments which follows this lecture
video.
Right?
I will put one of the questions in the assignment and I will describe that why it is necessary here
to use broadcast and not signal.
And what will be the problem you will get if you use signal instead of broadcast?
So guys, this completes the implementation of this consumer function.
Now let's go.
What is there in the producer flowchart?
So, guys, now going forward, the flowchart on the screen is the representation of the logic of the
producer function.
Right?
And you can see that the logic which the producer function follows is not very different from the flowchart
of the consumer thread, Right.
So more or less these are the same steps which we implemented in the consumer function.
So now let us implement this producer thread flowchart.
So once again, I will open up my terminal and I will going to implement the logic of the producer function
and this function that is producer function, right?
So as for the flow chart, the very first thing I would going to do is to log the Q.
So I will going to log the queue, right?
So whatever producer it is, whether it is GP one or GP2, it will first of all log the queue so that
it can inspect the state of the resource, which is nothing but queue of course and make a changes to
the state of the queue.
Then after getting an exclusive lock on the queue, the producer thread will check whether the queue
is full or not.
So here I'm doing the same thing.
That is, check the predicate within the while loop and in case if the queue is found full then our
producer thread really have nothing to produce and push it into the queue.
Right.
So it means that our producer thread must block itself, right?
So here you can see that the producer thread is blocking itself the same way the consumer thread blocked
right.
And our producer thread will execute beyond the line number 83 only when it will receive a signal from
the consumer thread, which essentially means that producer thread must inspect the state of the queue
once again before it performs its production of element operation on the queue.
Right.
So that is why we always do the predicate check within the while loop.
Once the producer thread wakes up from its blocked state, the producer thread must again check whether
the queue is full or not and it must block itself again if the predicate condition dictates it to do
so.
Right.
So here we are implementing the step number seven as per the flowchart, our producer thread will execute
beyond the line number 85.
Only when the producer thread has gotten access to the queue, which is not full and we need to check
whether we are following the constraint.
That producer thread must get an access to an empty queue only if it do not get an access to the empty
queue, then it means that we need to intentionally crash our program because we have violated the constraint
of our problem statement.
Right.
So our producer thread will execute beyond line number 88 only when it has gotten access to the queue,
which is completely empty.
Right.
So it means that we have entered into this box that is critical section in which the producer thread
will keep on generating new elements and insert into the queue until the queue is full.
So again, there is a need of the while loop and you can see that our producer thread is.
Creating a new integer by using this new int and it is pushing this new integer into the queue and the
producer thread would continue to do this until the queue is full.
Right.
So when the queue is full then our producer thread has to send a broadcast signal so that the consumer
thread which are in the blocked state can resume their execution.
Right.
So in the step number eight, our producer thread has to send the broadcast signal.
And finally, before the producer thread terminates, it has to release the lock on the queue.
Right.
And this completes the implementation of the producer flowchart, right.
The things are pretty much simple and straightforward.
So, guys, now that we have discussed the implementation of producer and Consumer Flowchart, you should
compare this implementation against your own implementation, which you did and see.
What mistakes did you make, if any?
Right?
You should critically analyze your solution and see what mistakes you made and why.
It is absolutely fine that while writing multithreaded program and implementing thread synchronization
based solution.
You would definitely make a mistake and your program may enter into a dialogue or you or your program
may not produce the expected output until or unless you make a mistake.
While writing thread synchronization based problems, you would not going to learn thread synchronization.
The concept of thread synchronization is one of the most difficult topics in the field of computer science,
and the more mistakes you make, the more reasons you would know that why your program entered into
deadlock or unexpected output.
Making mistakes is a way to learn.
Thread synchronization and master this skill.
So as we progress towards implementing more sophisticated problems on thread synchronization in this
course, it is very much expected that you can end up writing a code that is not perfect and may have
thread synchronization glitches.
But this is how the concept of thread synchronization is learned and mastered.
Right?
Make mistakes and learn from the mistakes.
So now after this lecture, video is an assignment which you should really do.
All the questions in that assignment is based on this producer consumer problem.
And in the assignment you would go into critically analyze this producer consumer solution and you must
not skip the assignments because the assignment will going to check your understanding of thread synchronization
topics that we have covered so far in this course.