WEBVTT 1 00:00:00.140 --> 00:00:01.510 Hi, and welcome back! 2 00:00:01.510 --> 00:00:02.620 In this video we're going to look at 3 00:00:02.620 --> 00:00:07.240 a couple example codes from what we covered in the last one. 4 00:00:07.240 --> 00:00:08.860 So I've created a new project in Python, 5 00:00:08.860 --> 00:00:10.660 I've called it concurrency. 6 00:00:10.660 --> 00:00:12.810 And we're gonna create a new file in here, 7 00:00:12.810 --> 00:00:17.120 a new Python file that I'm gonna call threads. 8 00:00:17.120 --> 00:00:18.920 Okay, by the way, this code is available 9 00:00:18.920 --> 00:00:22.200 linked at the start of the section 10 00:00:22.200 --> 00:00:25.070 which is on the GitHub for this course. 11 00:00:26.320 --> 00:00:28.770 So, let's say you've got some code 12 00:00:28.770 --> 00:00:31.810 that you want to run, like we saw earlier, 13 00:00:31.810 --> 00:00:33.760 that asks the user for something 14 00:00:33.760 --> 00:00:36.560 and then some other code does a complex calculation. 15 00:00:37.690 --> 00:00:39.540 Okay, we're gonna have two functions: 16 00:00:40.670 --> 00:00:42.000 the ask_user function, 17 00:00:43.320 --> 00:00:46.680 this is going to get some input from the user, 18 00:00:48.610 --> 00:00:51.090 and then is gonna construct this greeting 19 00:00:54.370 --> 00:00:56.170 and finally just gonna print it out. 20 00:00:58.330 --> 00:01:00.430 This is what this function does. 21 00:01:00.430 --> 00:01:05.190 And as you know, this function here is going to wait 22 00:01:05.190 --> 00:01:07.220 for the user to type something. 23 00:01:08.200 --> 00:01:09.033 You know, okay? 24 00:01:09.033 --> 00:01:11.630 These functions here, or these statements here, 25 00:01:11.630 --> 00:01:14.350 are not going to wait for anything. 26 00:01:14.350 --> 00:01:18.000 We'd say that this is a blocking operation, 27 00:01:18.000 --> 00:01:20.940 an operation where your thread is blocked, 28 00:01:20.940 --> 00:01:22.690 waiting for something to happen. 29 00:01:24.040 --> 00:01:27.400 So these operations are what makes your threaded code slow. 30 00:01:27.400 --> 00:01:28.980 OK, don't worry about that too much, 31 00:01:28.980 --> 00:01:31.440 we're gonna explore this concept more and more 32 00:01:31.440 --> 00:01:32.590 over the coming videos. 33 00:01:34.080 --> 00:01:35.290 Then you're also going to have 34 00:01:35.290 --> 00:01:37.230 a complex calculation function. 35 00:01:38.660 --> 00:01:41.100 That is going to do something like 36 00:01:41.100 --> 00:01:43.330 print started calculating, 37 00:01:44.450 --> 00:01:46.430 and then it's going to do something that takes a while. 38 00:01:46.430 --> 00:01:49.000 It's just gonna do something pretty silly, 39 00:01:49.000 --> 00:01:54.000 x to the power of two for x in range 20 million. 40 00:01:55.740 --> 00:01:56.810 Okay, twenty million? 41 00:01:57.900 --> 00:01:59.350 Yeah, 20 million will do. 42 00:01:59.350 --> 00:02:01.190 So this should take about six or seven seconds 43 00:02:01.190 --> 00:02:02.590 in my machine. 44 00:02:02.590 --> 00:02:04.620 It could take longer or shorter in your machine, 45 00:02:04.620 --> 00:02:06.710 just tweak the number accordingly. 46 00:02:07.780 --> 00:02:11.970 Okay, so it's gonna do that and that's it. 47 00:02:11.970 --> 00:02:13.890 Then we're gonna do ask_user first 48 00:02:15.270 --> 00:02:17.670 and then we're gonna do our complex calculation. 49 00:02:18.780 --> 00:02:21.540 Now, of course we're not gonna know how long they take 50 00:02:21.540 --> 00:02:23.610 unless we start timing them. 51 00:02:23.610 --> 00:02:26.920 So, we're gonna import time at the top 52 00:02:26.920 --> 00:02:30.850 and all I'm gonna do is, every function, time it. 53 00:02:30.850 --> 00:02:32.780 So we're gonna do start = time.time() 54 00:02:34.658 --> 00:02:39.658 and at the end we're gonna print ask_user. 55 00:02:39.699 --> 00:02:44.699 ask_user time.time minus start. 56 00:02:46.920 --> 00:02:50.790 Okay, same thing for the complex calculation. 57 00:02:58.660 --> 00:03:03.090 Now, I'm also going to do this for the whole programme 58 00:03:03.090 --> 00:03:07.600 so I'm gonna track how long the whole programme took. 59 00:03:18.160 --> 00:03:19.550 Like that, okay? 60 00:03:20.880 --> 00:03:23.280 Again, all that's happening here is 61 00:03:23.280 --> 00:03:25.780 first of all, we start a timer, 62 00:03:25.780 --> 00:03:27.470 then we launch the ask_user function 63 00:03:27.470 --> 00:03:29.640 which starts a timer inside that function, 64 00:03:29.640 --> 00:03:33.120 asks the user for some input, waits for the user to type, 65 00:03:33.120 --> 00:03:35.380 constructs a new string and prints it out 66 00:03:35.380 --> 00:03:37.550 and then it's gonna print the end time. 67 00:03:38.810 --> 00:03:41.730 Then it's gonna launch the complex calculation function, 68 00:03:41.730 --> 00:03:43.200 which is gonna start a timer, 69 00:03:43.200 --> 00:03:45.170 print that it started calculating, 70 00:03:45.170 --> 00:03:46.670 go through this list comprehension which is 71 00:03:46.670 --> 00:03:49.780 a pretty massive one, and when it's done 72 00:03:49.780 --> 00:03:51.120 it's going to print out. 73 00:03:51.120 --> 00:03:53.750 Now the complex calculation took a certain amount of time. 74 00:03:53.750 --> 00:03:55.700 And then at the very end, we're gonna print 75 00:03:55.700 --> 00:03:58.650 that the single thread took some amount of time. 76 00:03:58.650 --> 00:04:01.240 You know from all the Python development 77 00:04:01.240 --> 00:04:02.630 we've been doing up to here, 78 00:04:02.630 --> 00:04:04.630 that this is going to run from top to bottom. 79 00:04:04.630 --> 00:04:06.120 You know, this is gonna run first, 80 00:04:06.120 --> 00:04:08.490 then this one, our execution is gonna jump to here, 81 00:04:08.490 --> 00:04:10.900 then here, then here, then it's gonna go back, 82 00:04:10.900 --> 00:04:12.990 and then it's gonna go here and so forth. 83 00:04:12.990 --> 00:04:15.650 This is a single thread. 84 00:04:15.650 --> 00:04:18.570 This is our normal Python runtime. 85 00:04:18.570 --> 00:04:20.660 We should write our code and it runs 86 00:04:20.660 --> 00:04:22.320 from top to bottom because the thread is 87 00:04:22.320 --> 00:04:24.100 this line of execution that 88 00:04:24.100 --> 00:04:27.920 we've given to the CPU core to run. 89 00:04:27.920 --> 00:04:31.810 So if we run this file, if we run threads, 90 00:04:31.810 --> 00:04:32.643 the first thing it's gonna do, 91 00:04:32.643 --> 00:04:34.770 is ask me my name, I'm gonna type it in, 92 00:04:36.300 --> 00:04:38.410 oh, and I think I put one too few zero's here, 93 00:04:38.410 --> 00:04:39.540 didn't take long enough. 94 00:04:39.540 --> 00:04:41.040 And notice how the complex calculation 95 00:04:41.040 --> 00:04:43.140 took point six seconds, I wanna take it, 96 00:04:43.140 --> 00:04:45.740 I want it to take six seconds, so I'm gonna run it again. 97 00:04:47.600 --> 00:04:50.380 So, that's my name and it took one point six seconds 98 00:04:50.380 --> 00:04:54.120 to run the ask_user function and it started calculating. 99 00:04:54.120 --> 00:04:58.380 It took about 6.4 seconds to run the complex calculation. 100 00:04:58.380 --> 00:05:01.560 And in total, it adds these two together essentially, 101 00:05:01.560 --> 00:05:03.390 plus a little bit of time, not a lot of it, 102 00:05:03.390 --> 00:05:07.200 but a slight amount, the single thread took 8.14. 103 00:05:08.550 --> 00:05:12.280 This slight amount of extra it takes is because, 104 00:05:12.280 --> 00:05:15.310 you know, it has to jump from here to up to the function, 105 00:05:15.310 --> 00:05:18.100 and that takes a couple nanoseconds. 106 00:05:18.100 --> 00:05:20.740 So it's not gonna be an exact addition. 107 00:05:20.740 --> 00:05:24.520 But you know, overall it's sequential. 108 00:05:24.520 --> 00:05:27.460 First we run this, then we run this, 109 00:05:27.460 --> 00:05:30.160 and the total is more or less the addition of both. 110 00:05:31.790 --> 00:05:32.623 Now, okay. 111 00:05:33.640 --> 00:05:35.140 Now, what we're gonna do is, 112 00:05:35.140 --> 00:05:36.740 I'm going to collapse these functions. 113 00:05:36.740 --> 00:05:38.850 This does not delete them, it just hides the codes in them 114 00:05:38.850 --> 00:05:40.150 so it's a bit easier for you to see. 115 00:05:40.150 --> 00:05:42.630 They're still doing the same thing, OK. 116 00:05:42.630 --> 00:05:44.130 And what we're gonna do then 117 00:05:44.130 --> 00:05:47.380 is, from threading import Thread. 118 00:05:48.470 --> 00:05:50.650 So we're gonna import the thread class 119 00:05:50.650 --> 00:05:51.880 from the threading module. 120 00:05:51.880 --> 00:05:53.280 The threading module comes with Python, 121 00:05:53.280 --> 00:05:54.800 you don't have to instal anything. 122 00:05:54.800 --> 00:05:57.080 This gives us access to threads. 123 00:05:57.940 --> 00:05:59.460 Whenever we create a thread, 124 00:05:59.460 --> 00:06:01.080 that's gonna go to the operating system 125 00:06:01.080 --> 00:06:02.650 and it's gonna ask the operating system 126 00:06:02.650 --> 00:06:05.610 to give us a new thread and the operating system 127 00:06:05.610 --> 00:06:07.750 is gonna be like, okay here you go, new thread, 128 00:06:07.750 --> 00:06:09.550 you can run whatever you want in it. 129 00:06:10.750 --> 00:06:12.240 And what we're gonna run 130 00:06:12.240 --> 00:06:17.240 is we're gonna say thread one is Thread 131 00:06:17.850 --> 00:06:22.420 and the target is going to be our complex calculation 132 00:06:22.420 --> 00:06:24.960 Notice that the target is the complex calculation function 133 00:06:24.960 --> 00:06:27.410 not the result of it. 134 00:06:27.410 --> 00:06:29.100 So we're not gonna run the function here, 135 00:06:29.100 --> 00:06:31.060 we're just gonna pass it into the thread 136 00:06:31.060 --> 00:06:33.560 and when we ask the thread to start, 137 00:06:33.560 --> 00:06:34.720 what it's gonna do is it's gonna 138 00:06:34.720 --> 00:06:39.220 start a new thread running just this complex calculation. 139 00:06:39.220 --> 00:06:40.800 And thread two is gonna be a new thread 140 00:06:40.800 --> 00:06:43.670 where the target is the ask_user function. 141 00:06:44.560 --> 00:06:48.000 Now OK, so now we have three threads: 142 00:06:48.000 --> 00:06:49.490 a main thread, which is responsible 143 00:06:49.490 --> 00:06:51.480 for running through the app, 144 00:06:51.480 --> 00:06:52.430 a thread which is responsible 145 00:06:52.430 --> 00:06:54.330 for running the complex calculation, 146 00:06:54.330 --> 00:06:55.730 and another thread which is responsible 147 00:06:55.730 --> 00:06:57.310 for running the ask_user function. 148 00:06:57.310 --> 00:06:59.400 So we're gonna run these two at the same time 149 00:06:59.400 --> 00:07:00.710 and we're gonna see what happens, okay? 150 00:07:00.710 --> 00:07:05.040 The resolute is to do time.time, measure the start time, 151 00:07:05.040 --> 00:07:10.040 thread.start, sorry thread1.start(), thread2.start(). 152 00:07:13.090 --> 00:07:14.700 Now these threads have started 153 00:07:16.110 --> 00:07:19.180 and they are running at the same time as our main thread. 154 00:07:19.180 --> 00:07:21.930 Okay, so now we have three threads running at the same time. 155 00:07:21.930 --> 00:07:23.160 When we start this one, 156 00:07:23.160 --> 00:07:25.140 the complex calculation function is running. 157 00:07:25.140 --> 00:07:28.240 When we start this one, the ask_user function is running. 158 00:07:28.240 --> 00:07:30.650 And also, our main thread which is responsible 159 00:07:30.650 --> 00:07:33.330 for running this code, is also running. 160 00:07:33.330 --> 00:07:36.010 So we have to tell our main thread to wait 161 00:07:36.010 --> 00:07:38.190 for these two threads to finish. 162 00:07:38.190 --> 00:07:43.190 The way we do that: thread1.join() and thread2.join(). 163 00:07:46.630 --> 00:07:49.070 This tells our main thread to wait 164 00:07:49.070 --> 00:07:52.580 for thread one to finish and wait for thread two to finish. 165 00:07:52.580 --> 00:07:54.960 Again, these here are what we would call 166 00:07:54.960 --> 00:07:57.330 blocking operations because they're are making 167 00:07:57.330 --> 00:08:00.520 this thread wait until something happens. 168 00:08:00.520 --> 00:08:01.840 But in this case we want to do that 169 00:08:01.840 --> 00:08:04.000 because we want to wait until the threads finish 170 00:08:04.000 --> 00:08:06.340 before we can calculate the end time of the thread. 171 00:08:06.340 --> 00:08:10.340 So we're gonna say two thread total time 172 00:08:11.190 --> 00:08:14.470 is time.time minus start. 173 00:08:15.950 --> 00:08:16.783 Now OK. 174 00:08:18.360 --> 00:08:19.310 This is fine. 175 00:08:19.310 --> 00:08:20.700 Don't worry about that yellow thing. 176 00:08:20.700 --> 00:08:23.460 Let's run this again and see what happens. 177 00:08:23.460 --> 00:08:25.560 What do you think is gonna happen? 178 00:08:27.030 --> 00:08:29.380 Think about it before we continue. 179 00:08:31.480 --> 00:08:33.730 Ask me for my name, took a couple of seconds to do that 180 00:08:33.730 --> 00:08:35.470 and it starts calculating, 181 00:08:36.690 --> 00:08:37.523 and in the end it's gonna take 182 00:08:37.523 --> 00:08:39.640 more or less the same amount as before, eight seconds, 183 00:08:39.640 --> 00:08:41.440 then it asks for my name again, 184 00:08:41.440 --> 00:08:43.820 it took a bit longer now, just to type, 185 00:08:44.740 --> 00:08:49.240 but notice how in total, it took 6.45 seconds 186 00:08:49.240 --> 00:08:52.130 instead of 8.36 seconds. 187 00:08:53.040 --> 00:08:54.680 But the complex calculation 188 00:08:54.680 --> 00:08:56.730 took essentially the same amount of time. 189 00:08:58.070 --> 00:08:59.770 The ask_user took even longer 190 00:09:01.130 --> 00:09:05.020 but this is fine because in total, 191 00:09:05.020 --> 00:09:09.120 we took as long as the longest running function 192 00:09:09.120 --> 00:09:11.670 and that is because the ask_user function 193 00:09:11.670 --> 00:09:15.070 didn't do anything except wait, okay? 194 00:09:16.560 --> 00:09:19.860 The difference between these two times is 195 00:09:19.860 --> 00:09:23.880 how much CPU the ask_user function needed 196 00:09:23.880 --> 00:09:27.650 plus how long it took us to switch between the threads 197 00:09:27.650 --> 00:09:31.380 in the CPU, in the processor in your course. 198 00:09:32.460 --> 00:09:34.020 As you can see, not a lot of it. 199 00:09:34.020 --> 00:09:36.980 In total, the two-threaded time took more or less 200 00:09:36.980 --> 00:09:39.180 the same amount of time as your complex calculation 201 00:09:39.180 --> 00:09:40.560 because your ask_user function 202 00:09:40.560 --> 00:09:43.380 didn't actually need the CPU for anything. 203 00:09:43.380 --> 00:09:45.380 It just created a new string, that's it. 204 00:09:47.020 --> 00:09:48.010 So hopefully this makes sense, 205 00:09:48.010 --> 00:09:49.580 this is what threads are for. 206 00:09:51.130 --> 00:09:53.210 It's for reducing waiting time. 207 00:09:53.210 --> 00:09:56.930 When you have a blocking operation like this input, 208 00:09:56.930 --> 00:10:00.360 that is waiting, making your programme wait for something, 209 00:10:00.360 --> 00:10:02.970 that's a good use for a thread. 210 00:10:02.970 --> 00:10:05.620 If you have a function like this complex calculation, 211 00:10:06.820 --> 00:10:09.730 using a thread is not such a great idea. 212 00:10:09.730 --> 00:10:13.210 And here is a final example for you. 213 00:10:13.210 --> 00:10:15.270 You know, threaded code, 214 00:10:15.270 --> 00:10:18.500 I'm going to not use the ask_user function. 215 00:10:20.010 --> 00:10:22.160 I'm going to use the complex calculation function again. 216 00:10:22.160 --> 00:10:23.780 So now we will have two threads, 217 00:10:23.780 --> 00:10:25.950 both running the complex calculation. 218 00:10:25.950 --> 00:10:27.810 Separate instances of it, but they're both 219 00:10:27.810 --> 00:10:29.510 going to calculate the same thing. 220 00:10:30.520 --> 00:10:32.630 Okay, let's run this again. 221 00:10:32.630 --> 00:10:33.960 It'll ask me for my name first because 222 00:10:33.960 --> 00:10:36.330 that's the single-threaded code 223 00:10:36.330 --> 00:10:38.540 and now let's see what happens with our calculation. 224 00:10:38.540 --> 00:10:41.350 Remember, only one thread can run at a time, okay? 225 00:10:41.350 --> 00:10:42.930 So you cannot run both at the same time 226 00:10:42.930 --> 00:10:44.770 but we get started calculating twice 227 00:10:44.770 --> 00:10:46.590 because this is two functions that are running, 228 00:10:46.590 --> 00:10:48.660 each in one thread. 229 00:10:48.660 --> 00:10:50.210 And now let's see what happens. 230 00:10:53.160 --> 00:10:54.710 Notice how it's taking a while. 231 00:10:55.630 --> 00:10:57.370 There you have it. 232 00:10:57.370 --> 00:10:59.180 Complex calculation now took 233 00:10:59.180 --> 00:11:02.030 double the amount of time to run 234 00:11:02.030 --> 00:11:05.190 because they cannot run at the same time, 235 00:11:05.190 --> 00:11:07.780 so the operating system was sort of 236 00:11:07.780 --> 00:11:09.380 giving a little bit of time to one, 237 00:11:09.380 --> 00:11:10.640 then pulling it out and giving 238 00:11:10.640 --> 00:11:12.070 a little bit of time to the other, 239 00:11:12.070 --> 00:11:14.140 then pulling it out and going back to the first one, 240 00:11:14.140 --> 00:11:15.820 then going to the second one and so forth, 241 00:11:15.820 --> 00:11:17.900 switching between the two 242 00:11:17.900 --> 00:11:21.300 but all of them needed all that time in the CPU 243 00:11:21.300 --> 00:11:23.070 to calculate things. 244 00:11:23.070 --> 00:11:26.010 So in total, what you did is, you spread out the time 245 00:11:26.010 --> 00:11:27.540 that each one was running. 246 00:11:27.540 --> 00:11:31.890 And you sort of interspersed one with the other 247 00:11:31.890 --> 00:11:34.490 continuously and in the end it took double the time. 248 00:11:35.910 --> 00:11:39.710 If you had run these sequentially without threads, 249 00:11:39.710 --> 00:11:41.570 it would actually be faster because 250 00:11:41.570 --> 00:11:43.660 you wouldn't have to deal with 251 00:11:43.660 --> 00:11:46.070 removing one thread and putting another one in. 252 00:11:46.070 --> 00:11:49.460 So if you are doing things that require the CPU 253 00:11:49.460 --> 00:11:51.980 all the time, don't use threads. 254 00:11:51.980 --> 00:11:54.150 It's pointless in Python. 255 00:11:54.150 --> 00:11:56.950 There are other ways of doing similar things 256 00:11:56.950 --> 00:11:59.540 as we will learn, that could be better. 257 00:12:00.890 --> 00:12:03.410 Now we're gonna go back to our ask_user function here, 258 00:12:03.410 --> 00:12:06.080 again this code is available on the GitHub, 259 00:12:06.080 --> 00:12:07.930 please download it, play around with it, 260 00:12:07.930 --> 00:12:10.590 run it so you, you know, make a few changes, 261 00:12:10.590 --> 00:12:11.750 have a think. 262 00:12:11.750 --> 00:12:14.840 This stuff is complicated stuff, okay? 263 00:12:14.840 --> 00:12:17.830 There's a reason why not many courses go into this 264 00:12:17.830 --> 00:12:20.920 because it's difficult and I am expecting 265 00:12:20.920 --> 00:12:22.350 you guys to come to the course Q&A 266 00:12:22.350 --> 00:12:25.400 and ask questions and, you know, I'm here to help you out 267 00:12:25.400 --> 00:12:27.110 if anything is unclear. 268 00:12:28.190 --> 00:12:31.160 So please do use that resource. 269 00:12:31.160 --> 00:12:34.240 I want you to leave this course knowing about this stuff. 270 00:12:35.500 --> 00:12:37.190 So that's it for this video, thanks for joining me 271 00:12:37.190 --> 00:12:39.440 in this one and I'll see you on the next one.