WEBVTT

00:06.420 --> 00:07.980
So welcome back, guys.

00:07.980 --> 00:12.390
Now, in this lecture video, we will going to implement the solution of our producer consumer problem

00:12.390 --> 00:15.930
statement that we have discussed in the previous lecture videos.

00:15.930 --> 00:22.260
So I hope you have given enough trials regarding implementing these producer and consumer flowcharts.

00:22.530 --> 00:27.780
So in this lecture video, let us implement these producer and consumer flowcharts step by step.

00:27.780 --> 00:30.780
So we will start with implementation of consumer Thread.

00:30.810 --> 00:32.250
Flowchart Right.

00:32.880 --> 00:39.090
So what I will going to do is that on the screen you can see that I am in the file assignment producer

00:39.090 --> 00:42.060
Consumer on solutions, right?

00:42.060 --> 00:44.490
This is the file which implements the solution.

00:44.490 --> 00:48.900
And first of all, we will going to provide the implementation of this consumer function.

00:48.900 --> 00:49.740
Right?

00:49.740 --> 00:56.130
Remember, this consumer function would going to be executed by two consumer threads concurrently.

00:56.130 --> 00:56.850
Right?

00:56.850 --> 01:02.190
Similarly, this producer function will be executed by two producer threads concurrently.

01:02.460 --> 01:04.050
So we will do no new thing.

01:04.050 --> 01:09.880
We will just stick to this flowchart and we will going to implement the steps as described in this flowchart.

01:09.910 --> 01:10.750
Right?

01:10.930 --> 01:18.160
So revise the problem statement because we will going to implement the problem statement such that the

01:18.160 --> 01:24.250
constraints which are described by the problem statement must not get violated in our solution.

01:24.250 --> 01:25.000
Right.

01:26.160 --> 01:30.920
So consumer thread starts with a step S1 That is the consumer thread.

01:30.930 --> 01:34.470
First of all, locks the queue while the consumer thread locks the queue.

01:34.500 --> 01:40.980
In fact, any thread which inspect the state of the data structure or make changes to the state of the

01:40.980 --> 01:47.120
data structure, that particular thread must first of all get an explicit lock on that data structure,

01:47.130 --> 01:47.850
right?

01:47.850 --> 01:54.180
So step S1 is all about locking the mutex of the queue, right?

01:55.720 --> 02:02.020
So starting our implementation of the consumer function, you can see that in the step S1 I'm simply

02:02.020 --> 02:04.030
locking the queue, right?

02:04.840 --> 02:06.670
So this is the step S1.

02:09.170 --> 02:14.330
Now, since in the step S1, the consumer thread has already logged the data structure.

02:14.330 --> 02:19.640
Now consumer thread is in a position to inspect the state of the data structure, right?

02:19.670 --> 02:26.240
Since the data structure has been logged in step S1 So it simply means that during the time when the

02:26.240 --> 02:31.910
consumer thread is inspecting the state of the data structure, no other thread can change the state

02:31.910 --> 02:33.650
of the data structure, right?

02:33.680 --> 02:38.000
This is what the principle of mutual exclusion says, right?

02:38.150 --> 02:42.950
So in step number S2, the consumer thread is checking whether the queue is empty or not.

02:42.980 --> 02:48.980
If the queue is empty, then consumer thread really do not have anything to consume and consumer thread

02:48.980 --> 02:52.400
will block itself in step number S3 Right.

02:53.900 --> 02:55.130
Whereas.

02:55.780 --> 03:03.640
If this queue is not empty right then our consumer thread will check whether the queue is full and if

03:03.640 --> 03:07.140
the queue is not full then we need to crash our program.

03:07.150 --> 03:07.990
Right.

03:08.080 --> 03:14.320
If you remember one of the constraint of our producer consumer problem statement was that that the consumer

03:14.320 --> 03:17.950
must get an access to the queue only when the queue is full.

03:18.070 --> 03:23.020
The consumer thread must not get an access to the queue, which is half filled.

03:23.020 --> 03:23.770
Right.

03:24.610 --> 03:31.150
So if the consumer thread gets an access to the queue and if the queue is not full, then it means that

03:31.150 --> 03:34.270
we have violated the constraint of our problem statement.

03:34.270 --> 03:39.300
And this is something that we do not want our program to land in such a situation.

03:39.310 --> 03:43.540
Therefore, we will going to crash our program intentionally.

03:43.540 --> 03:44.320
Right?

03:45.010 --> 03:52.480
However, in case if the queue is full, then our consumer thread now is in a position to actually start

03:52.480 --> 03:57.350
consuming the elements from this queue one by one until the queue is empty.

03:57.350 --> 04:00.350
So this is the step number four, right?

04:00.380 --> 04:04.400
So let us implement the step number S2, S3 and S4.

04:04.670 --> 04:10.880
So you can see here that our consumer thread has already got a lock on the queue.

04:10.880 --> 04:16.670
And now in step number S2, we are going to check whether the queue is empty or not.

04:16.970 --> 04:23.750
So here we are checking the predicate condition that if the queue is empty right then it means that

04:23.750 --> 04:29.510
our consumer thread really has nothing to do and our consumer thread simply block itself.

04:29.540 --> 04:33.500
It will block itself by invoking the api p thread condition.

04:33.500 --> 04:34.160
Wait.

04:34.190 --> 04:35.000
Right.

04:35.860 --> 04:41.320
And you need to pass the condition variable of the queue as well as the mutex of the queue.

04:41.350 --> 04:42.040
Right.

04:42.040 --> 04:49.630
So immediately after executing the step number three, this particular mutex which is logged in step

04:49.630 --> 04:53.230
number s one would be unlocked automatically.

04:53.230 --> 05:00.520
So the consumer thread will block at the step number s three if it finds the queue already empty and

05:00.520 --> 05:07.960
our consumer thread will proceed or resume its execution beyond line number 91 only when it receives

05:07.960 --> 05:10.900
a signal from some other thread of the program.

05:10.900 --> 05:14.500
In that case, our consumer thread would be woken up.

05:14.710 --> 05:15.490
Right.

05:15.490 --> 05:19.180
And it should check the predicate condition again.

05:19.570 --> 05:20.340
Right.

05:20.350 --> 05:22.090
This is what we have already discussed.

05:22.090 --> 05:26.860
That checking of the predicate condition must be done in a while loop.

05:27.400 --> 05:28.120
Right?

05:29.390 --> 05:34.700
So this logic implements the step number S2 and step number S3.

05:34.820 --> 05:41.600
And once our consumer thread comes out of this while loop, it simply means that the queue is no more

05:41.600 --> 05:42.370
empty.

05:42.380 --> 05:43.220
Right?

05:43.250 --> 05:49.040
So now it's time for our consumer thread to check if the queue is full.

05:49.280 --> 05:55.730
And here you can see that we are simply crashing our program if the queue is not full.

05:55.940 --> 05:56.780
Right.

05:57.380 --> 06:02.660
We have a constraint in our problem statement that consumer must start consuming the elements from the

06:02.660 --> 06:03.860
full queue only.

06:03.860 --> 06:04.610
Right.

06:05.780 --> 06:10.610
So here we have implemented this assert box in the flowchart.

06:10.820 --> 06:19.550
And going further now the consumer thread will resume its execution beyond the line number 96.

06:19.700 --> 06:22.070
Only when the queue is full.

06:22.100 --> 06:22.780
Right?

06:22.790 --> 06:26.540
It means now it's time for us to implement the step number.

06:26.650 --> 06:28.520
S4 Right.

06:28.910 --> 06:38.570
So in step number S4, what I will going to do is that our consumer thread would going to consume an

06:38.570 --> 06:44.120
element from the queue one by one within a while loop until the queue becomes empty.

06:44.330 --> 06:45.170
Right.

06:45.290 --> 06:51.710
So you can see that our consumer thread is iterating over the queue and every time it is removing one

06:51.710 --> 06:57.650
element from the queue and we are simply printing that an element has been removed or consumed from

06:57.650 --> 06:58.250
the queue.

06:58.250 --> 07:01.790
And we are also printing which element has been removed from the queue.

07:01.820 --> 07:02.600
Right.

07:02.600 --> 07:09.780
Printing this information would give you a useful insight that what actually your program is doing and

07:09.780 --> 07:16.380
in case if your program encounters certain deadlocks or erroneous output, these logs would help you

07:16.380 --> 07:19.440
to root cause the problem and fix it right?

07:20.040 --> 07:27.150
So you can see that the line numbers from 100 to 104 implements this particular box that is consume

07:27.150 --> 07:29.820
all the elements of the queue until the queue is empty.

07:29.820 --> 07:38.850
And as soon as the queue becomes empty, our consumer thread will go beyond this while loop and as per

07:38.850 --> 07:40.980
the flowchart we need to send.

07:41.650 --> 07:43.540
Signal, right?

07:44.860 --> 07:48.820
We need to send a signal on the condition variable of the resource.

07:49.450 --> 07:53.920
And finally, the step number s five says simply unlocks the queue.

07:54.670 --> 08:01.810
So here I will going to unlock the mutex on the queue because the consumer thread has completed its

08:01.840 --> 08:03.130
operation on the queue.

08:03.250 --> 08:04.030
Right.

08:04.640 --> 08:09.650
So you can see that the consumer thread after unlocking the mutex on the queue.

08:09.680 --> 08:15.800
The consumer thread exits because it has completed its operation, which it was supposed to perform

08:15.800 --> 08:16.370
on the queue.

08:16.760 --> 08:17.590
Right.

08:17.600 --> 08:23.030
So you can see that I have successfully implemented this complete flowchart as a consumer function.

08:23.690 --> 08:30.210
Right thing here to note is that that we always check the state of the resource within a while loop.

08:30.230 --> 08:35.750
This is you can say that this is one of the rule of the thread synchronization, right?

08:36.710 --> 08:42.380
And one of the question that must be crossing your mind is that that why are we using p thread condition

08:42.380 --> 08:45.260
broadcast and not p thread condition signal?

08:45.290 --> 08:50.840
Well, the answer to this question you will get while doing the assignments which follows this lecture

08:50.840 --> 08:51.560
video.

08:51.560 --> 08:52.430
Right?

08:52.460 --> 08:58.670
I will put one of the questions in the assignment and I will describe that why it is necessary here

08:58.670 --> 09:00.620
to use broadcast and not signal.

09:00.620 --> 09:05.930
And what will be the problem you will get if you use signal instead of broadcast?

09:06.800 --> 09:10.700
So guys, this completes the implementation of this consumer function.

09:10.940 --> 09:12.350
Now let's go.

09:12.380 --> 09:14.570
What is there in the producer flowchart?

09:17.650 --> 09:23.140
So, guys, now going forward, the flowchart on the screen is the representation of the logic of the

09:23.140 --> 09:24.310
producer function.

09:24.580 --> 09:25.320
Right?

09:25.330 --> 09:31.870
And you can see that the logic which the producer function follows is not very different from the flowchart

09:31.900 --> 09:34.360
of the consumer thread, Right.

09:34.600 --> 09:39.340
So more or less these are the same steps which we implemented in the consumer function.

09:39.340 --> 09:42.580
So now let us implement this producer thread flowchart.

09:43.750 --> 09:50.080
So once again, I will open up my terminal and I will going to implement the logic of the producer function

09:50.080 --> 09:52.600
and this function that is producer function, right?

09:53.690 --> 09:58.370
So as for the flow chart, the very first thing I would going to do is to log the Q.

09:58.400 --> 10:01.670
So I will going to log the queue, right?

10:01.670 --> 10:07.670
So whatever producer it is, whether it is GP one or GP2, it will first of all log the queue so that

10:07.670 --> 10:13.190
it can inspect the state of the resource, which is nothing but queue of course and make a changes to

10:13.190 --> 10:14.450
the state of the queue.

10:14.750 --> 10:19.370
Then after getting an exclusive lock on the queue, the producer thread will check whether the queue

10:19.370 --> 10:20.570
is full or not.

10:20.720 --> 10:22.730
So here I'm doing the same thing.

10:22.730 --> 10:29.330
That is, check the predicate within the while loop and in case if the queue is found full then our

10:29.330 --> 10:33.530
producer thread really have nothing to produce and push it into the queue.

10:33.560 --> 10:34.250
Right.

10:34.250 --> 10:38.540
So it means that our producer thread must block itself, right?

10:39.470 --> 10:44.570
So here you can see that the producer thread is blocking itself the same way the consumer thread blocked

10:44.600 --> 10:45.440
right.

10:45.590 --> 10:52.940
And our producer thread will execute beyond the line number 83 only when it will receive a signal from

10:52.940 --> 10:58.890
the consumer thread, which essentially means that producer thread must inspect the state of the queue

10:58.920 --> 11:04.920
once again before it performs its production of element operation on the queue.

11:05.430 --> 11:06.240
Right.

11:06.240 --> 11:11.560
So that is why we always do the predicate check within the while loop.

11:11.580 --> 11:17.700
Once the producer thread wakes up from its blocked state, the producer thread must again check whether

11:17.700 --> 11:25.050
the queue is full or not and it must block itself again if the predicate condition dictates it to do

11:25.050 --> 11:25.620
so.

11:25.650 --> 11:26.430
Right.

11:26.640 --> 11:33.570
So here we are implementing the step number seven as per the flowchart, our producer thread will execute

11:33.570 --> 11:35.490
beyond the line number 85.

11:35.490 --> 11:44.040
Only when the producer thread has gotten access to the queue, which is not full and we need to check

11:44.040 --> 11:46.080
whether we are following the constraint.

11:46.080 --> 11:52.470
That producer thread must get an access to an empty queue only if it do not get an access to the empty

11:52.470 --> 12:00.030
queue, then it means that we need to intentionally crash our program because we have violated the constraint

12:00.030 --> 12:01.560
of our problem statement.

12:01.560 --> 12:02.340
Right.

12:05.420 --> 12:12.020
So our producer thread will execute beyond line number 88 only when it has gotten access to the queue,

12:12.020 --> 12:14.060
which is completely empty.

12:14.090 --> 12:14.900
Right.

12:14.900 --> 12:20.720
So it means that we have entered into this box that is critical section in which the producer thread

12:20.720 --> 12:25.910
will keep on generating new elements and insert into the queue until the queue is full.

12:26.030 --> 12:33.020
So again, there is a need of the while loop and you can see that our producer thread is.

12:34.830 --> 12:42.600
Creating a new integer by using this new int and it is pushing this new integer into the queue and the

12:42.600 --> 12:46.260
producer thread would continue to do this until the queue is full.

12:46.590 --> 12:47.340
Right.

12:47.640 --> 12:54.480
So when the queue is full then our producer thread has to send a broadcast signal so that the consumer

12:54.480 --> 12:58.350
thread which are in the blocked state can resume their execution.

12:58.350 --> 12:59.160
Right.

12:59.460 --> 13:04.770
So in the step number eight, our producer thread has to send the broadcast signal.

13:04.890 --> 13:11.160
And finally, before the producer thread terminates, it has to release the lock on the queue.

13:11.370 --> 13:12.210
Right.

13:13.260 --> 13:18.000
And this completes the implementation of the producer flowchart, right.

13:18.030 --> 13:20.700
The things are pretty much simple and straightforward.

13:20.820 --> 13:26.490
So, guys, now that we have discussed the implementation of producer and Consumer Flowchart, you should

13:26.490 --> 13:31.800
compare this implementation against your own implementation, which you did and see.

13:31.800 --> 13:34.420
What mistakes did you make, if any?

13:34.420 --> 13:34.980
Right?

13:34.990 --> 13:40.810
You should critically analyze your solution and see what mistakes you made and why.

13:41.170 --> 13:47.380
It is absolutely fine that while writing multithreaded program and implementing thread synchronization

13:47.380 --> 13:48.520
based solution.

13:49.220 --> 13:55.040
You would definitely make a mistake and your program may enter into a dialogue or you or your program

13:55.040 --> 14:00.410
may not produce the expected output until or unless you make a mistake.

14:00.410 --> 14:06.330
While writing thread synchronization based problems, you would not going to learn thread synchronization.

14:06.350 --> 14:13.130
The concept of thread synchronization is one of the most difficult topics in the field of computer science,

14:13.130 --> 14:19.280
and the more mistakes you make, the more reasons you would know that why your program entered into

14:19.280 --> 14:21.470
deadlock or unexpected output.

14:22.100 --> 14:24.450
Making mistakes is a way to learn.

14:24.470 --> 14:26.840
Thread synchronization and master this skill.

14:28.060 --> 14:34.810
So as we progress towards implementing more sophisticated problems on thread synchronization in this

14:34.810 --> 14:41.050
course, it is very much expected that you can end up writing a code that is not perfect and may have

14:41.050 --> 14:43.420
thread synchronization glitches.

14:43.420 --> 14:47.860
But this is how the concept of thread synchronization is learned and mastered.

14:47.860 --> 14:48.580
Right?

14:48.610 --> 14:51.310
Make mistakes and learn from the mistakes.

14:53.360 --> 14:59.090
So now after this lecture, video is an assignment which you should really do.

14:59.090 --> 15:03.380
All the questions in that assignment is based on this producer consumer problem.

15:03.380 --> 15:10.430
And in the assignment you would go into critically analyze this producer consumer solution and you must

15:10.430 --> 15:16.340
not skip the assignments because the assignment will going to check your understanding of thread synchronization

15:16.340 --> 15:19.310
topics that we have covered so far in this course.
