So, guys, now going forward, now that we have discussed enough theory, now it's time to do some
practical.
So now let us implement producer consumer problem statement.
So you can see on the screen that I'm in the directory path Multithreading Bible slash producer consumer.
Now in this directory, if you list all the files, you will find that this is the file which contains
the problem statement, and it is this file in which you will have to write code.
And I have already provided the solution which is implemented in this file, which is assignment producer,
consumer on solution dot C and this is the shell script.
Running this shell script will compile all the source file and create an executable exe and solution
dot exe.
Right.
Solution dot exe is the executable which you can execute in order to see how the solution program will
eventually going to work.
Whereas EXE is the executable which is produced from this assignment file and it is this executable
which you need to execute in order to test your program.
Right?
And that we have discussed that we will going to implement producer consumer problem statement on a
shared data structure which is queue.
So this is the queue dot C and Q dot h file which has been provided to you for use, Right.
So I would encourage you to not to have a look at this solution dot C source file because this file
already contains the solution that we will going to discuss.
I would strongly discourage you to see the solution and try your best to actually implement the producer
consumer problem statement by yourself first.
Right?
Now if you open the file assignment producer consumer on Q dot C.
Now, as I said, this is the file which contains the problem statement, right?
So we will going to discuss the structure of this source file.
That is what this file actually contains.
So you can see that this is the problem statement that we have already discussed on the slides, Right?
So there is no difference.
And let us start our discussion from the main function straightaway.
So this is a very short file, contains only 134 lines.
And in the main function, what we do is that first of all, we create a shared resource which is a
queue.
So this queue is a global variable.
Here you can see that I have taken a pointer to the queue as a global variable in this file and in the
main function, the first thing I am doing is to simply initialize the queue right.
So this function would take care to initialize the mutex and condition variable which is associated
with this queue.
So if you want to take an internal implementation of this init function though you don't need it.
You can simply see that the queue has been initialized and the mutex as well as the condition variable
which is associated with this queue has been initialized.
Right.
We have already discussed the structure of this queue data structure.
It contains a mutex and the condition variable.
These are the two members which you are supposed to use in your program while implementing producer
consumer logic.
Right.
The rest of the things you need not worry because we will not go into access any of these members of
the queue because we will have these APIs to help us, right?
We will going to access the data structure through APIs that have been provided to us from this queue
library.
Right.
Then going further in this main function, you can see that I have taken four threads.
The two threads are producer thread and the other two threads are consumer threads.
So you can see that I have taken four thread handle and then after that I am creating four threads one
by one all the four threads I have been creating in a joinable mode.
Right?
Because you can see that I am passing the same attributes while creating all the four threads.
So all the four threads have been created in the joinable mode.
Right.
Then the last argument to each of these p thread create function is actually a name of the thread.
So you can see that producer one, producer two consumer one and consumer two are nothing, but they
are just a pointer to the strings, right?
So these variable actually represents the name of the threads.
As we discussed in our slide, the name of the threads are tp1, tp2, tc1 and Tc2.
Right.
So as a final argument to this p thread underscore create API, I am passing the name of the thread
in the context of which this producer and consumer functions will going to execute right.
So you can see that I have created two producer threads which are associated with the producer function,
and I have created two consumer threads which is associated with this consumer function.
Right.
So if you take a look at the implementation of this producer and consumer function, you can see that
they are having empty implementation.
It is our responsibility to actually provide the implementation of this function.
And providing the implementation of this function essentially means that we are implementing producer
consumer problems with two producers and two consumers.
Right?
The argument to each of this function is actually a name of the thread, right?
So whatever logic you implement in the producer or in the consumer function, you should insert as many
printf statements as possible so that.
Whatever activity you are, producer thread or consumer thread is doing will be displayed on the screen,
right?
You would know that exactly what is being going on between producer and consumer thread.
And just in case, if your program enters into a deadlock situation by analyzing the logs printed by
a program, you can actually root cause it that what actually caused the deadlocks.
Right?
So whatever printf statements you insert in a producer or consumer function, those printf statements
must print the name of the thread along with the activity that the thread is doing so that you know
that exactly which producer or exactly which consumer thread is doing what activity.
Right?
And once the producer and consumer thread finished their task, all the four producer and consumer threads
joins the main thread again.
Right.
So in case if your program enters into a deadlock situation, that is.
Produce or consume a threat has entered into a deadlock situation than that particular producer or consumer
threat would never come and join back the main thread.
It simply means that if your program ever enters into a deadlock situation, your program will not going
to finish this printf statement in line 131.
Right?
Your program will never print program finished string on the screen if your program enters into a deadlock
condition.
Right?
So this is the overall structure of this program and you need to implement this producer and consumer
function.
And also note that you can join us on the Telegram Group with this Telegram group ID the name of the
Telegram Group ID is Teli Practicals.
Also, one more thing that I would like to point out is that that we discussed that your producer thread
has to produce an element and push it into the queue.
So now, as per our problem statement, that element which is produced by the producer thread is nothing,
but it is just a integer value.
So in order to create a new integer value, I have simply implemented a function, the invocation of
which will give you a new incremented integer value.
Right.
So producer thread can make use of this function in order to create a new integer value every time so
that it can push that integer value into the queue.
Right.
And also note that the maximum size of the queue is five.
It means that your producer thread must not push more than five elements into the queue.
Right.
So for that you can make use of these API that is SQ empty or full in order to check whether the queue
is full or empty.
Right.
So now that we have understood the structure of this program and we have understood what we are supposed
to do.
So now let us briefly discuss that, what logic you actually need to implement in the producer function
and consumer function, right?
Note that producer function is invoked by the two producer thread.
Tp1.
Tp2.
And the consumer function will be invoked by the consumer thread.
Tc1 and Tc2.
So guys, the logic that you need to implement in the consumer function in the assignment is represented
by this flowchart, Right.
So I would leave it to you to analyze and study this flowchart by yourself and try to implement the
consumer function by yourself.
If you split the steps shown in this flowchart, you will see that this flowchart can exactly be split
it up into the steps S1, S2, S3, S4 and S5, which now we have been discussing for a while in this
course.
Right.
In the next lecture video, I will show you how to implement this flowchart in a step by step manner.
So essentially, in the next lecture video, I will going to implement the solution.
But I would strongly encourage you to try implementing this solution by yourself.
Right?
So this is the flowchart for consumer thread.
Similarly, this is the flowchart for producer thread.
So you need to implement exactly these steps in the producer function and you can see that this flowchart
also can be divided into same standard steps S6, S7, S8 and S9, which we have been studying.
Right.
So analyze the flowcharts of producer and consumer threat and try to implement by yourself in the assignment.
And then, guys, finally, if you compile this whole assignment, you will find the solution dot exe
executable created.
If you execute this executable, you will see the program will launch producer and the consumer thread
and you will see the output like this on the screen.
Right.
So let me discuss this output in brief so that you get a clear idea regarding what you actually need
to implement as a part of producer and consumer functions.
So you can see that every line of this log starts with the name of the thread.
As I said earlier, that every printf statement that you insert must print the name of the thread so
that you would know that which thread is doing what activity.
Right?
So here you can see that the thread Tpp1 Tpp1 means that first producer thread.
The first producer thread has logged the queue and as per the problem statement, whenever the producer
thread logs a queue, it will fill the entire queue with the integers generated from the function nu
int.
So you can see that the producer thread tpp1 is filling up the queue until the queue is full.
We also remember that the maximum size of the queue is five.
Therefore the producer thread tpp1 has filled the elements 12345 into the queue.
Right.
And once the queue is full, then the producer thread actually finishes and acts it and the queue is
then logged by the consumer thread.
Now the job of the consumer thread is to remove the elements from the queue until the queue is empty.
Right.
So you can see that the consumer thread tc1 has drained out all the elements from the queue and once
the queue becomes empty then the queue is logged by another producer.
Thread Tp2.
Now this time the producer thread Tp2 again fills up the queue with the elements six, seven, eight,
nine and ten.
Right.
And the job of the producer thread tp2 is done and now the queue is claimed by the consumer thread Tc2
which removes the element 6789 ten from the queue again.
Right.
So you can see that all the threads tp1, tp2 and tc1 and Tc2 have performed their respective tasks
on the queue and in the end the queue should be left empty.
Right.
And because all the threads have finished their task, all the threads then join the main function.
And finally the string program finished is printed on the screen.
Right.
Since this string is printed on the screen, we can very well say that our program has not encountered
any deadlock situation.
All the threads.
All the threads have performed their respective tasks and finished their execution gracefully.
Right.
So if you notice the sequence, the queue is filled with the values.
One, two, three, four, five by the producer thread Tp1.
And then consumer thread.
Tc1 has drained out the same elements from the queue so that queue becomes empty again.
Then again, the producer thread Tpx2 locks the queue and fill the element.
Six, seven, eight, 910 into the queue and again the consumer thread Tc2 drains out all the elements
from the queue and eventually the queue is empty and eventually all the threads have finished their
execution and the program finishes.
So now you understand that what you are supposed to do.
So you also have to print the similar output on the screen and you should evaluate the output of your
program against this output and evaluate that your program exactly does the same thing, which the problem
statement dictates.
And guys, finally, all these slides have been attached to the resource section of this lecture video.
Please refer to these slides while implementing your solution.