WEBVTT

00:05.920 --> 00:11.140
So, guys, now going forward, now that we have discussed enough theory, now it's time to do some

00:11.140 --> 00:11.960
practical.

00:11.980 --> 00:15.310
So now let us implement producer consumer problem statement.

00:15.340 --> 00:21.700
So you can see on the screen that I'm in the directory path Multithreading Bible slash producer consumer.

00:22.180 --> 00:28.720
Now in this directory, if you list all the files, you will find that this is the file which contains

00:28.720 --> 00:32.640
the problem statement, and it is this file in which you will have to write code.

00:32.650 --> 00:37.660
And I have already provided the solution which is implemented in this file, which is assignment producer,

00:37.660 --> 00:42.340
consumer on solution dot C and this is the shell script.

00:42.340 --> 00:48.790
Running this shell script will compile all the source file and create an executable exe and solution

00:48.790 --> 00:49.450
dot exe.

00:49.720 --> 00:50.470
Right.

00:51.280 --> 00:58.270
Solution dot exe is the executable which you can execute in order to see how the solution program will

00:58.270 --> 00:59.800
eventually going to work.

00:59.890 --> 01:06.620
Whereas EXE is the executable which is produced from this assignment file and it is this executable

01:06.620 --> 01:09.740
which you need to execute in order to test your program.

01:09.920 --> 01:10.760
Right?

01:11.660 --> 01:16.520
And that we have discussed that we will going to implement producer consumer problem statement on a

01:16.520 --> 01:18.620
shared data structure which is queue.

01:18.650 --> 01:24.710
So this is the queue dot C and Q dot h file which has been provided to you for use, Right.

01:25.280 --> 01:32.600
So I would encourage you to not to have a look at this solution dot C source file because this file

01:32.600 --> 01:35.360
already contains the solution that we will going to discuss.

01:35.900 --> 01:41.690
I would strongly discourage you to see the solution and try your best to actually implement the producer

01:41.690 --> 01:44.120
consumer problem statement by yourself first.

01:44.120 --> 01:44.810
Right?

01:46.040 --> 01:50.270
Now if you open the file assignment producer consumer on Q dot C.

01:50.540 --> 01:54.860
Now, as I said, this is the file which contains the problem statement, right?

01:55.160 --> 01:58.130
So we will going to discuss the structure of this source file.

01:58.130 --> 02:01.520
That is what this file actually contains.

02:01.520 --> 02:07.130
So you can see that this is the problem statement that we have already discussed on the slides, Right?

02:07.130 --> 02:08.960
So there is no difference.

02:08.960 --> 02:13.700
And let us start our discussion from the main function straightaway.

02:13.700 --> 02:17.450
So this is a very short file, contains only 134 lines.

02:17.450 --> 02:23.420
And in the main function, what we do is that first of all, we create a shared resource which is a

02:23.420 --> 02:23.990
queue.

02:24.080 --> 02:27.170
So this queue is a global variable.

02:27.470 --> 02:34.460
Here you can see that I have taken a pointer to the queue as a global variable in this file and in the

02:34.460 --> 02:38.900
main function, the first thing I am doing is to simply initialize the queue right.

02:39.950 --> 02:45.110
So this function would take care to initialize the mutex and condition variable which is associated

02:45.110 --> 02:46.010
with this queue.

02:47.090 --> 02:53.060
So if you want to take an internal implementation of this init function though you don't need it.

02:53.090 --> 03:00.500
You can simply see that the queue has been initialized and the mutex as well as the condition variable

03:00.500 --> 03:03.260
which is associated with this queue has been initialized.

03:03.290 --> 03:04.040
Right.

03:04.220 --> 03:07.820
We have already discussed the structure of this queue data structure.

03:07.820 --> 03:10.770
It contains a mutex and the condition variable.

03:10.790 --> 03:16.040
These are the two members which you are supposed to use in your program while implementing producer

03:16.040 --> 03:17.120
consumer logic.

03:17.120 --> 03:17.840
Right.

03:17.930 --> 03:23.180
The rest of the things you need not worry because we will not go into access any of these members of

03:23.180 --> 03:27.320
the queue because we will have these APIs to help us, right?

03:27.350 --> 03:34.190
We will going to access the data structure through APIs that have been provided to us from this queue

03:34.190 --> 03:35.000
library.

03:35.000 --> 03:35.720
Right.

03:38.200 --> 03:43.780
Then going further in this main function, you can see that I have taken four threads.

03:43.780 --> 03:47.970
The two threads are producer thread and the other two threads are consumer threads.

03:47.980 --> 03:54.640
So you can see that I have taken four thread handle and then after that I am creating four threads one

03:54.640 --> 04:00.010
by one all the four threads I have been creating in a joinable mode.

04:00.100 --> 04:00.870
Right?

04:00.880 --> 04:06.070
Because you can see that I am passing the same attributes while creating all the four threads.

04:06.070 --> 04:09.400
So all the four threads have been created in the joinable mode.

04:09.400 --> 04:10.240
Right.

04:10.420 --> 04:16.210
Then the last argument to each of these p thread create function is actually a name of the thread.

04:16.240 --> 04:23.350
So you can see that producer one, producer two consumer one and consumer two are nothing, but they

04:23.350 --> 04:26.920
are just a pointer to the strings, right?

04:26.920 --> 04:30.850
So these variable actually represents the name of the threads.

04:30.880 --> 04:37.240
As we discussed in our slide, the name of the threads are tp1, tp2, tc1 and Tc2.

04:37.270 --> 04:38.320
Right.

04:38.320 --> 04:44.320
So as a final argument to this p thread underscore create API, I am passing the name of the thread

04:44.350 --> 04:50.680
in the context of which this producer and consumer functions will going to execute right.

04:51.230 --> 04:56.360
So you can see that I have created two producer threads which are associated with the producer function,

04:56.360 --> 05:01.700
and I have created two consumer threads which is associated with this consumer function.

05:01.910 --> 05:02.830
Right.

05:02.840 --> 05:09.020
So if you take a look at the implementation of this producer and consumer function, you can see that

05:09.020 --> 05:11.750
they are having empty implementation.

05:11.750 --> 05:16.790
It is our responsibility to actually provide the implementation of this function.

05:16.790 --> 05:21.770
And providing the implementation of this function essentially means that we are implementing producer

05:21.770 --> 05:25.910
consumer problems with two producers and two consumers.

05:26.090 --> 05:26.840
Right?

05:26.870 --> 05:31.850
The argument to each of this function is actually a name of the thread, right?

05:32.830 --> 05:39.460
So whatever logic you implement in the producer or in the consumer function, you should insert as many

05:39.460 --> 05:42.370
printf statements as possible so that.

05:42.930 --> 05:49.140
Whatever activity you are, producer thread or consumer thread is doing will be displayed on the screen,

05:49.140 --> 05:49.920
right?

05:49.950 --> 05:54.810
You would know that exactly what is being going on between producer and consumer thread.

05:54.810 --> 06:01.500
And just in case, if your program enters into a deadlock situation by analyzing the logs printed by

06:01.500 --> 06:06.450
a program, you can actually root cause it that what actually caused the deadlocks.

06:06.450 --> 06:07.200
Right?

06:07.200 --> 06:13.830
So whatever printf statements you insert in a producer or consumer function, those printf statements

06:13.830 --> 06:19.680
must print the name of the thread along with the activity that the thread is doing so that you know

06:19.680 --> 06:24.750
that exactly which producer or exactly which consumer thread is doing what activity.

06:24.750 --> 06:25.470
Right?

06:25.770 --> 06:32.040
And once the producer and consumer thread finished their task, all the four producer and consumer threads

06:32.040 --> 06:34.110
joins the main thread again.

06:34.260 --> 06:35.070
Right.

06:35.100 --> 06:39.840
So in case if your program enters into a deadlock situation, that is.

06:40.570 --> 06:46.420
Produce or consume a threat has entered into a deadlock situation than that particular producer or consumer

06:46.420 --> 06:49.870
threat would never come and join back the main thread.

06:49.900 --> 06:56.650
It simply means that if your program ever enters into a deadlock situation, your program will not going

06:56.650 --> 07:00.210
to finish this printf statement in line 131.

07:00.220 --> 07:00.900
Right?

07:00.910 --> 07:08.590
Your program will never print program finished string on the screen if your program enters into a deadlock

07:08.590 --> 07:09.430
condition.

07:09.430 --> 07:10.120
Right?

07:11.500 --> 07:17.320
So this is the overall structure of this program and you need to implement this producer and consumer

07:17.320 --> 07:18.190
function.

07:18.940 --> 07:24.880
And also note that you can join us on the Telegram Group with this Telegram group ID the name of the

07:24.880 --> 07:28.060
Telegram Group ID is Teli Practicals.

07:28.930 --> 07:35.440
Also, one more thing that I would like to point out is that that we discussed that your producer thread

07:35.470 --> 07:39.460
has to produce an element and push it into the queue.

07:39.700 --> 07:45.220
So now, as per our problem statement, that element which is produced by the producer thread is nothing,

07:45.220 --> 07:47.380
but it is just a integer value.

07:47.410 --> 07:54.040
So in order to create a new integer value, I have simply implemented a function, the invocation of

07:54.040 --> 07:57.210
which will give you a new incremented integer value.

07:57.220 --> 07:58.040
Right.

07:58.060 --> 08:05.470
So producer thread can make use of this function in order to create a new integer value every time so

08:05.470 --> 08:08.380
that it can push that integer value into the queue.

08:08.470 --> 08:09.280
Right.

08:09.460 --> 08:13.760
And also note that the maximum size of the queue is five.

08:13.940 --> 08:20.180
It means that your producer thread must not push more than five elements into the queue.

08:20.210 --> 08:20.990
Right.

08:21.230 --> 08:28.400
So for that you can make use of these API that is SQ empty or full in order to check whether the queue

08:28.400 --> 08:29.590
is full or empty.

08:29.600 --> 08:30.350
Right.

08:30.710 --> 08:35.690
So now that we have understood the structure of this program and we have understood what we are supposed

08:35.690 --> 08:36.450
to do.

08:36.470 --> 08:42.530
So now let us briefly discuss that, what logic you actually need to implement in the producer function

08:42.530 --> 08:44.750
and consumer function, right?

08:45.110 --> 08:48.650
Note that producer function is invoked by the two producer thread.

08:48.680 --> 08:49.160
Tp1.

08:49.160 --> 08:49.940
Tp2.

08:49.940 --> 08:54.020
And the consumer function will be invoked by the consumer thread.

08:54.050 --> 08:56.030
Tc1 and Tc2.

08:58.430 --> 09:04.640
So guys, the logic that you need to implement in the consumer function in the assignment is represented

09:04.640 --> 09:06.500
by this flowchart, Right.

09:06.530 --> 09:13.490
So I would leave it to you to analyze and study this flowchart by yourself and try to implement the

09:13.490 --> 09:15.240
consumer function by yourself.

09:15.260 --> 09:22.370
If you split the steps shown in this flowchart, you will see that this flowchart can exactly be split

09:22.370 --> 09:29.180
it up into the steps S1, S2, S3, S4 and S5, which now we have been discussing for a while in this

09:29.180 --> 09:29.930
course.

09:29.930 --> 09:30.680
Right.

09:30.950 --> 09:35.780
In the next lecture video, I will show you how to implement this flowchart in a step by step manner.

09:35.780 --> 09:39.590
So essentially, in the next lecture video, I will going to implement the solution.

09:39.590 --> 09:44.150
But I would strongly encourage you to try implementing this solution by yourself.

09:44.150 --> 09:45.000
Right?

09:45.020 --> 09:47.890
So this is the flowchart for consumer thread.

09:47.900 --> 09:51.110
Similarly, this is the flowchart for producer thread.

09:51.290 --> 09:58.260
So you need to implement exactly these steps in the producer function and you can see that this flowchart

09:58.260 --> 10:05.610
also can be divided into same standard steps S6, S7, S8 and S9, which we have been studying.

10:05.730 --> 10:06.510
Right.

10:07.670 --> 10:14.030
So analyze the flowcharts of producer and consumer threat and try to implement by yourself in the assignment.

10:14.390 --> 10:21.260
And then, guys, finally, if you compile this whole assignment, you will find the solution dot exe

10:21.290 --> 10:22.700
executable created.

10:22.730 --> 10:29.600
If you execute this executable, you will see the program will launch producer and the consumer thread

10:29.600 --> 10:32.960
and you will see the output like this on the screen.

10:32.960 --> 10:33.770
Right.

10:34.280 --> 10:39.740
So let me discuss this output in brief so that you get a clear idea regarding what you actually need

10:39.740 --> 10:42.980
to implement as a part of producer and consumer functions.

10:43.100 --> 10:49.460
So you can see that every line of this log starts with the name of the thread.

10:49.580 --> 10:55.850
As I said earlier, that every printf statement that you insert must print the name of the thread so

10:55.850 --> 10:58.850
that you would know that which thread is doing what activity.

10:58.850 --> 10:59.630
Right?

10:59.630 --> 11:04.850
So here you can see that the thread Tpp1 Tpp1 means that first producer thread.

11:04.880 --> 11:11.040
The first producer thread has logged the queue and as per the problem statement, whenever the producer

11:11.040 --> 11:18.030
thread logs a queue, it will fill the entire queue with the integers generated from the function nu

11:18.030 --> 11:18.690
int.

11:18.720 --> 11:24.240
So you can see that the producer thread tpp1 is filling up the queue until the queue is full.

11:24.270 --> 11:28.020
We also remember that the maximum size of the queue is five.

11:28.050 --> 11:34.650
Therefore the producer thread tpp1 has filled the elements 12345 into the queue.

11:34.740 --> 11:35.520
Right.

11:37.150 --> 11:43.870
And once the queue is full, then the producer thread actually finishes and acts it and the queue is

11:43.870 --> 11:46.180
then logged by the consumer thread.

11:46.210 --> 11:52.390
Now the job of the consumer thread is to remove the elements from the queue until the queue is empty.

11:52.390 --> 11:53.230
Right.

11:53.260 --> 12:00.490
So you can see that the consumer thread tc1 has drained out all the elements from the queue and once

12:00.490 --> 12:05.800
the queue becomes empty then the queue is logged by another producer.

12:05.800 --> 12:07.120
Thread Tp2.

12:07.930 --> 12:14.290
Now this time the producer thread Tp2 again fills up the queue with the elements six, seven, eight,

12:14.290 --> 12:15.460
nine and ten.

12:15.610 --> 12:16.480
Right.

12:16.570 --> 12:24.400
And the job of the producer thread tp2 is done and now the queue is claimed by the consumer thread Tc2

12:24.430 --> 12:28.900
which removes the element 6789 ten from the queue again.

12:29.350 --> 12:30.100
Right.

12:30.100 --> 12:37.760
So you can see that all the threads tp1, tp2 and tc1 and Tc2 have performed their respective tasks

12:37.760 --> 12:41.780
on the queue and in the end the queue should be left empty.

12:41.810 --> 12:42.590
Right.

12:42.590 --> 12:48.500
And because all the threads have finished their task, all the threads then join the main function.

12:48.500 --> 12:53.060
And finally the string program finished is printed on the screen.

12:53.300 --> 12:54.050
Right.

12:54.080 --> 13:00.500
Since this string is printed on the screen, we can very well say that our program has not encountered

13:00.500 --> 13:01.970
any deadlock situation.

13:02.000 --> 13:03.380
All the threads.

13:03.770 --> 13:09.350
All the threads have performed their respective tasks and finished their execution gracefully.

13:09.380 --> 13:10.190
Right.

13:10.430 --> 13:15.770
So if you notice the sequence, the queue is filled with the values.

13:15.770 --> 13:18.860
One, two, three, four, five by the producer thread Tp1.

13:18.860 --> 13:20.270
And then consumer thread.

13:20.300 --> 13:27.140
Tc1 has drained out the same elements from the queue so that queue becomes empty again.

13:27.170 --> 13:32.150
Then again, the producer thread Tpx2 locks the queue and fill the element.

13:32.150 --> 13:39.200
Six, seven, eight, 910 into the queue and again the consumer thread Tc2 drains out all the elements

13:39.200 --> 13:44.480
from the queue and eventually the queue is empty and eventually all the threads have finished their

13:44.480 --> 13:46.830
execution and the program finishes.

13:46.850 --> 13:50.420
So now you understand that what you are supposed to do.

13:50.450 --> 13:56.450
So you also have to print the similar output on the screen and you should evaluate the output of your

13:56.450 --> 14:04.340
program against this output and evaluate that your program exactly does the same thing, which the problem

14:04.340 --> 14:05.720
statement dictates.

14:06.730 --> 14:12.790
And guys, finally, all these slides have been attached to the resource section of this lecture video.

14:12.790 --> 14:16.120
Please refer to these slides while implementing your solution.
