WEBVTT 1 00:00:00.280 --> 00:00:01.270 Hi and welcome back. 2 00:00:01.270 --> 00:00:03.350 In this video, we're going to look at running 3 00:00:03.350 --> 00:00:06.660 multiple threads at once and sharing 4 00:00:06.660 --> 00:00:09.090 some state between them. 5 00:00:09.090 --> 00:00:11.008 Okay, so I've cleared the new file, which I've called 6 00:00:11.008 --> 00:00:14.920 nonlockedthreads.py, but before we start writing some code, 7 00:00:14.920 --> 00:00:17.670 I just wanted to sort of tell you about something. 8 00:00:17.670 --> 00:00:21.460 And that is, what is an atomic operation? 9 00:00:21.460 --> 00:00:23.040 Atomic operations are very important 10 00:00:23.040 --> 00:00:25.440 when you're doing multi threaded codes. 11 00:00:25.440 --> 00:00:28.340 An atomic operation is one that cannot be interrupted 12 00:00:28.340 --> 00:00:30.210 in the middle of it. 13 00:00:30.210 --> 00:00:32.130 So you cannot interrupt an atomic operation 14 00:00:32.130 --> 00:00:35.810 halfway through it by changing to a new thread, okay? 15 00:00:35.810 --> 00:00:37.660 Whenever you're doing an atomic operation 16 00:00:37.660 --> 00:00:40.923 you know that it's going to finish before you can 17 00:00:40.923 --> 00:00:43.910 unplug the thread from the core and put another one in. 18 00:00:43.910 --> 00:00:47.070 For example, a print statement cannot print 19 00:00:47.070 --> 00:00:49.560 only half the line and then be interrupted. 20 00:00:50.520 --> 00:00:54.410 Similarly, appending to a deque or a dq if you're like me 21 00:00:54.410 --> 00:00:57.110 and you like saying that, appending to a deque 22 00:00:57.110 --> 00:00:59.190 cannot be interrupted halfway through. 23 00:00:59.190 --> 00:01:00.910 If you start appending to a deque, you say, 24 00:01:00.910 --> 00:01:04.262 you know, deque.append, you know that the thing will 25 00:01:04.262 --> 00:01:06.510 appended to the deque before you remove 26 00:01:06.510 --> 00:01:09.010 the thread from the core, okay? 27 00:01:09.010 --> 00:01:11.220 So atomic operations are very important 28 00:01:11.220 --> 00:01:14.110 when you have this shared state between threads. 29 00:01:15.780 --> 00:01:17.730 And we're gonna look at an example of where we're gonna 30 00:01:17.730 --> 00:01:20.100 have a function that's not atomic 31 00:01:20.100 --> 00:01:22.300 and why that poses a problem. 32 00:01:22.300 --> 00:01:25.170 Okay, so let's do that. 33 00:01:26.430 --> 00:01:29.440 I'm going to do, from threading import threads, 34 00:01:29.440 --> 00:01:31.870 I'm going to use good old threads instead of the 35 00:01:31.870 --> 00:01:33.670 thread pull executor, just for this, 36 00:01:34.940 --> 00:01:38.240 I think it's gonna be a bit easier to see what's going on. 37 00:01:38.240 --> 00:01:41.030 We're gonna import time and we're also gonna import random 38 00:01:41.030 --> 00:01:43.190 because we're gonna be randomising some things. 39 00:01:43.190 --> 00:01:45.780 Then, we're going to have a global variable in our file, 40 00:01:45.780 --> 00:01:49.360 which is going to be zero, okay? 41 00:01:49.360 --> 00:01:50.440 And then we're gonna have a function 42 00:01:50.440 --> 00:01:52.040 called increment counter. 43 00:01:53.530 --> 00:01:54.830 And what this is going to do, is it's gonna say, 44 00:01:54.830 --> 00:01:57.627 global counter, access the global variable counter, 45 00:01:57.627 --> 00:01:59.637 do counter plus equal one, 46 00:01:59.637 --> 00:02:04.290 and then print new counter value is counter. 47 00:02:05.220 --> 00:02:07.990 And then it's also going to print a few lines 48 00:02:09.290 --> 00:02:11.410 just so we can separate new counter values, 49 00:02:11.410 --> 00:02:13.540 from one before the other. 50 00:02:13.540 --> 00:02:16.350 Now, from what we know about single threaded code, 51 00:02:16.350 --> 00:02:18.590 if we do for x in range 10, 52 00:02:21.130 --> 00:02:23.390 and then we do increment counter, 53 00:02:23.390 --> 00:02:25.330 what do we think is going to happen? 54 00:02:29.310 --> 00:02:32.210 Hopefully you said, you know, what I would expect 55 00:02:32.210 --> 00:02:35.930 is that you start up here, x will be zero, you know, 56 00:02:35.930 --> 00:02:37.050 it's gonna run 10 times. 57 00:02:37.050 --> 00:02:39.000 The first time you're gonna go into increment counter, 58 00:02:39.000 --> 00:02:41.930 you're gonna access global counter, which is gonna be zero. 59 00:02:41.930 --> 00:02:44.700 You're gonna increase it by one, so it's gonna be one. 60 00:02:44.700 --> 00:02:46.910 You're gonna print out new counter value is one. 61 00:02:46.910 --> 00:02:48.310 You're gonna print out these lines. 62 00:02:48.310 --> 00:02:49.930 Then, you're gonna go back to the end of the function, 63 00:02:49.930 --> 00:02:52.530 you're gonna repeat you're loop, gonna go in here again, 64 00:02:52.530 --> 00:02:55.024 counter's gonna be one now, because it was increased before. 65 00:02:55.024 --> 00:02:57.490 Then you're gonna increase it by one going up to two. 66 00:02:57.490 --> 00:02:59.370 You're gonna print new counter value is two, 67 00:02:59.370 --> 00:03:01.190 and so forth, for three, four, five, six, 68 00:03:01.190 --> 00:03:02.500 seven, eight and so forth. 69 00:03:03.440 --> 00:03:05.430 So let's run this non locked threads, 70 00:03:05.430 --> 00:03:07.050 which is not using threads at all, 71 00:03:07.050 --> 00:03:09.240 and you can see that that is indeed what happens. 72 00:03:09.240 --> 00:03:14.030 You get one all the way down to 10, so good stuff. 73 00:03:14.030 --> 00:03:15.480 This is what we would expect. 74 00:03:16.710 --> 00:03:21.710 Now, what happens if we make a thread, 75 00:03:22.700 --> 00:03:25.150 we're saying t equals thread, 76 00:03:25.150 --> 00:03:27.350 and the target is increment counter? 77 00:03:28.700 --> 00:03:30.580 And then we're going to remove this increment counter, 78 00:03:30.580 --> 00:03:32.580 then we're going to do t.start. 79 00:03:34.350 --> 00:03:38.520 What do we think is gonna happen when we run this? 80 00:03:38.520 --> 00:03:42.150 Let me tell you that we're not gonna see any difference. 81 00:03:42.150 --> 00:03:44.540 Okay, we get one, two, three, four, five, six, seven 82 00:03:44.540 --> 00:03:45.900 all the way up to 10. 83 00:03:45.900 --> 00:03:48.770 Hm, okay, so, what was the purpose of threads 84 00:03:48.770 --> 00:03:50.670 and are they running at the same time? 85 00:03:53.790 --> 00:03:56.370 Using, writing multithreaded code is dangerous, 86 00:03:56.370 --> 00:04:01.370 because you may not see the problem straight away. 87 00:04:01.490 --> 00:04:04.920 The problem may only appear under some circumstances. 88 00:04:04.920 --> 00:04:07.810 Here is where time and random are gonna come in. 89 00:04:09.090 --> 00:04:10.754 What we've got here is, 90 00:04:10.754 --> 00:04:13.689 10 times we're gonna repeat this block of code. 91 00:04:13.689 --> 00:04:15.380 We're gonna create a new thread. 92 00:04:15.380 --> 00:04:17.430 The target is gonna be increment counter. 93 00:04:17.430 --> 00:04:19.560 And then we're gonna start thread, 94 00:04:19.560 --> 00:04:21.140 and then immediately we're gonna move over 95 00:04:21.140 --> 00:04:23.650 to the next thread and we're gonna start it, 96 00:04:23.650 --> 00:04:27.440 but this function is so quick to run that 97 00:04:27.440 --> 00:04:30.570 before we get to start the new thread, 98 00:04:30.570 --> 00:04:32.750 that function is already finished running. 99 00:04:32.750 --> 00:04:35.040 So essentially, this is sequential code. 100 00:04:35.040 --> 00:04:37.020 Essentially, it's single threaded code. 101 00:04:37.020 --> 00:04:39.120 Because you create a new thread, you started, 102 00:04:39.120 --> 00:04:40.780 by the time you created a new thread, 103 00:04:40.780 --> 00:04:41.760 that one's already finished, 104 00:04:41.760 --> 00:04:44.340 so essentially it's happening one after the other. 105 00:04:44.340 --> 00:04:49.000 What we're gonna do is we're gonna do time sleep 106 00:04:50.040 --> 00:04:53.620 random.random, okay? 107 00:04:53.620 --> 00:04:55.680 We're gonna put some time sleeps in here, 108 00:04:56.560 --> 00:05:00.310 essentially between each line, like that. 109 00:05:03.330 --> 00:05:07.340 This technique of adding random sleeps between statements 110 00:05:07.340 --> 00:05:10.520 when you're doing multithreaded codes is called fuzzying, 111 00:05:10.520 --> 00:05:13.250 its very popular, because it's gonna let us see problems. 112 00:05:13.250 --> 00:05:14.850 Okay, let's run this file again. 113 00:05:20.270 --> 00:05:22.540 Ooh, that's not looking so good now. 114 00:05:23.740 --> 00:05:25.810 So as you can see, what happened here now 115 00:05:25.810 --> 00:05:29.190 is that all our threads were running at the same time, 116 00:05:29.190 --> 00:05:31.950 because, you know, one of them started 117 00:05:31.950 --> 00:05:33.740 then it encountered this random sleep 118 00:05:33.740 --> 00:05:35.950 but by that time another one had maybe started 119 00:05:35.950 --> 00:05:37.640 and encountered another random sleep. 120 00:05:37.640 --> 00:05:39.260 You've got a few of them starting, you know, 121 00:05:39.260 --> 00:05:41.510 some of them are here, some of them are here, 122 00:05:42.720 --> 00:05:46.180 and what happens is, you've got a couple threads 123 00:05:47.100 --> 00:05:52.100 getting the counter value at three and increasing it by one, 124 00:05:52.190 --> 00:05:54.930 both at the same time, okay? 125 00:05:54.930 --> 00:05:57.400 So you've got two threads coming into the global counter. 126 00:05:57.400 --> 00:06:00.740 Two threads are stopped here, the counter is three. 127 00:06:00.740 --> 00:06:04.080 Then, they both increment counter by one at the same time. 128 00:06:04.080 --> 00:06:05.920 So it ends up as four. 129 00:06:05.920 --> 00:06:10.340 And then they both print out new counter values four, okay? 130 00:06:10.340 --> 00:06:12.180 So multithreaded code with shared state 131 00:06:12.180 --> 00:06:14.860 can be really tricky to get right, 132 00:06:14.860 --> 00:06:17.500 because two threads can try to access the same thing 133 00:06:17.500 --> 00:06:19.020 at the same time, and you know that's fine, 134 00:06:19.020 --> 00:06:21.320 you can access the same thing at the same time, 135 00:06:21.320 --> 00:06:23.690 but you may not end up with what you'd expect. 136 00:06:23.690 --> 00:06:26.349 Particularly if all the threads are changing something 137 00:06:26.349 --> 00:06:28.660 in this variable at the same time. 138 00:06:30.240 --> 00:06:32.820 And you may not see that unless you do this fizzying 139 00:06:32.820 --> 00:06:35.560 technique or you know, other techniques. 140 00:06:37.010 --> 00:06:39.780 So what we've got here are all our threads running, 141 00:06:39.780 --> 00:06:43.370 you know, they're messed up, essentially. 142 00:06:43.370 --> 00:06:46.010 We cannot proceed with this, 143 00:06:46.010 --> 00:06:48.920 and if you remove the fizzying it would work, 144 00:06:48.920 --> 00:06:50.939 until you know, your computer is running slowly 145 00:06:50.939 --> 00:06:53.830 or you've got a bunch of things running at the same time 146 00:06:53.830 --> 00:06:55.300 on your computer and these threads are jumping 147 00:06:55.300 --> 00:06:57.220 in and out of course very quickly. 148 00:06:57.220 --> 00:06:59.990 Then you may start encountering problems like this one. 149 00:07:00.830 --> 00:07:04.250 So, you must be careful when you write multithreaded code. 150 00:07:04.250 --> 00:07:06.100 You must be very careful. 151 00:07:06.100 --> 00:07:07.750 Especially if you threads are, 152 00:07:07.750 --> 00:07:09.270 really only if your threads are 153 00:07:09.270 --> 00:07:12.190 accessing or modifying shared state. 154 00:07:12.190 --> 00:07:14.250 Here the variable is the shared state, 155 00:07:14.250 --> 00:07:17.490 these threads have half that variable shared amongst them. 156 00:07:17.490 --> 00:07:18.890 Dangerous stuff, okay? 157 00:07:20.580 --> 00:07:22.710 This code, by the way, and also the code that follows 158 00:07:22.710 --> 00:07:27.430 is taken from a talk that one of the python developers, 159 00:07:27.430 --> 00:07:29.960 the creators of the python language made. 160 00:07:29.960 --> 00:07:31.160 I'm going to link you to this talk 161 00:07:31.160 --> 00:07:34.210 at the end of this section, it's really good. 162 00:07:34.210 --> 00:07:37.830 I just want to guide you through it first in my words, 163 00:07:37.830 --> 00:07:38.790 just to help you understand it, 164 00:07:38.790 --> 00:07:41.290 and then you can go and watch their talk later on. 165 00:07:42.320 --> 00:07:47.080 Okay, if we wanted to make sure the operations happen 166 00:07:47.080 --> 00:07:49.040 sequentially, like we want to do 167 00:07:49.040 --> 00:07:50.340 new counter value is one, 168 00:07:50.340 --> 00:07:52.380 then new counter value is two, three. 169 00:07:53.550 --> 00:07:57.130 Then we would have to do something much more complicated. 170 00:07:58.080 --> 00:07:59.940 But again, if you want to make sure the operations 171 00:07:59.940 --> 00:08:02.740 happen sequentially, don't use threads. 172 00:08:02.740 --> 00:08:04.380 That's the first thing. 173 00:08:04.380 --> 00:08:06.160 If you want to make sure the operations happen sequentially 174 00:08:06.160 --> 00:08:08.160 but you want to use threads, 175 00:08:08.160 --> 00:08:11.130 then you have to set up a queuing system, 176 00:08:11.130 --> 00:08:13.600 where each thread can perform the operation 177 00:08:13.600 --> 00:08:18.600 of incrementing the counter but they happen one by one. 178 00:08:19.510 --> 00:08:21.910 So it's going to be more complicated code, 179 00:08:21.910 --> 00:08:24.420 let's go over it in the very next video, 180 00:08:24.420 --> 00:08:28.410 how you may do queuing with a thread so that 181 00:08:28.410 --> 00:08:29.730 the operations happen sequentially. 182 00:08:29.730 --> 00:08:31.280 I'll see you in the next video.