1 00:00:06,540 --> 00:00:09,720 - Now, your mutex is going to allow you 2 00:00:09,720 --> 00:00:11,280 to create a block of code, 3 00:00:11,280 --> 00:00:12,890 or multiple lines of code, 4 00:00:12,890 --> 00:00:16,350 and treat that as one atomic operation. 5 00:00:16,350 --> 00:00:19,360 So this time we're just gonna use the same package, 6 00:00:19,360 --> 00:00:22,530 and since we're gonna be making lines of code atomic, 7 00:00:22,530 --> 00:00:24,890 I can go back to my int 32. 8 00:00:24,890 --> 00:00:28,270 Now I've made this mutex as a global variable here, 9 00:00:28,270 --> 00:00:29,860 we don't really use global variables, 10 00:00:29,860 --> 00:00:32,330 I'm just showing you mechanics right now. 11 00:00:32,330 --> 00:00:36,770 Most of the time a mutex should be a field in some struct, 12 00:00:36,770 --> 00:00:39,080 and once you have a mutex as a field in a struct 13 00:00:39,080 --> 00:00:41,100 that struct can no longer be copied. 14 00:00:41,100 --> 00:00:44,083 A copy of a mutex creates a new mutex, 15 00:00:44,083 --> 00:00:46,310 this is all value semantics here. 16 00:00:46,310 --> 00:00:48,240 So make sure that you never make copies 17 00:00:48,240 --> 00:00:51,670 of the values that have mutexes inside of 'em. 18 00:00:51,670 --> 00:00:54,530 But you see it's usable in its zero value state. 19 00:00:54,530 --> 00:00:57,010 So this mutex creates either what we call 20 00:00:57,010 --> 00:01:00,240 a critical section of code that is atomic. 21 00:01:00,240 --> 00:01:02,610 Let me kind of draw out a visual 22 00:01:02,610 --> 00:01:07,330 of what I mean by a critical section of code that is atomic. 23 00:01:07,330 --> 00:01:12,330 I like to think of mutexes as creating rooms in your code. 24 00:01:13,370 --> 00:01:15,960 And so we can use this mutex 25 00:01:15,960 --> 00:01:18,930 and say these lines of code, 26 00:01:18,930 --> 00:01:21,230 whatever they are, these lines of code, right? 27 00:01:22,140 --> 00:01:23,970 Multiple lines of code. 28 00:01:23,970 --> 00:01:27,829 We have to make sure that they always run atomically, 29 00:01:27,829 --> 00:01:29,850 one after the other with no interruption. 30 00:01:29,850 --> 00:01:32,410 So we're gonna put these lines of code in this room, 31 00:01:32,410 --> 00:01:34,960 and the scheduler kind of acts like a bouncer, 32 00:01:34,960 --> 00:01:36,680 just like if you went to a bar. 33 00:01:36,680 --> 00:01:38,470 All of these goroutines are people 34 00:01:38,470 --> 00:01:39,900 who are coming to this room, 35 00:01:39,900 --> 00:01:43,730 they all want in but only one goroutine, or person, 36 00:01:43,730 --> 00:01:45,580 is allowed in the room at any given time 37 00:01:45,580 --> 00:01:47,780 because if we allow multiple people in the room 38 00:01:47,780 --> 00:01:49,610 we're going to have our data race, right? 39 00:01:49,610 --> 00:01:50,480 We're gonna have our situation 40 00:01:50,480 --> 00:01:54,240 where we cannot be modifying, read and or modifying, 41 00:01:54,240 --> 00:01:55,810 the same memory location at the same time, 42 00:01:55,810 --> 00:01:57,130 or in this particular case, 43 00:01:57,130 --> 00:01:58,980 we can't let two goroutines execute 44 00:01:58,980 --> 00:02:01,490 these four instructions at the same time. 45 00:02:01,490 --> 00:02:03,450 So the scheduler acts as a bouncer. 46 00:02:03,450 --> 00:02:04,830 Now, when you get to the room, 47 00:02:04,830 --> 00:02:07,687 what you're going to do is ask the scheduler, 48 00:02:07,687 --> 00:02:10,967 "Hey, I want a lock, I want a lock, I want a lock, 49 00:02:10,967 --> 00:02:13,240 "I want a lock, I want a lock, I want a lock." 50 00:02:13,240 --> 00:02:17,420 And that lock call is going to be a blocking call, 51 00:02:17,420 --> 00:02:19,440 and only one goroutine can get the lock 52 00:02:19,440 --> 00:02:20,650 at any given time. 53 00:02:20,650 --> 00:02:22,537 So eventually the scheduler's gonna go, 54 00:02:22,537 --> 00:02:24,477 "Okay, you goroutine right here, 55 00:02:24,477 --> 00:02:26,877 "I'm gonna give you the lock. 56 00:02:26,877 --> 00:02:29,060 "You get the lock, you come in." 57 00:02:29,060 --> 00:02:30,990 Which means all the other goroutines 58 00:02:30,990 --> 00:02:33,200 are now waiting their turn. 59 00:02:33,200 --> 00:02:35,013 Now, don't assume that a goroutine 60 00:02:35,013 --> 00:02:37,090 that gets to the room before another 61 00:02:37,090 --> 00:02:38,400 is gonna go first. 62 00:02:38,400 --> 00:02:41,950 There are algorithms here to try to make things fair. 63 00:02:41,950 --> 00:02:44,800 But just because this goroutine 64 00:02:44,800 --> 00:02:46,210 got here before this goroutine 65 00:02:46,210 --> 00:02:48,910 doesn't mean this goroutine gets in the room first. 66 00:02:48,910 --> 00:02:50,130 So it's like when you go to the club, 67 00:02:50,130 --> 00:02:52,280 somebody shows up in a fancy car 68 00:02:52,280 --> 00:02:53,770 and they get in ahead of you. 69 00:02:53,770 --> 00:02:54,690 It's all the same. 70 00:02:54,690 --> 00:02:56,490 It is not deterministic, 71 00:02:56,490 --> 00:02:59,150 which goroutine, when all things are equal here, 72 00:02:59,150 --> 00:03:01,040 is gonna get into the room first. 73 00:03:01,040 --> 00:03:03,110 Though the algorithm is doing its best 74 00:03:03,110 --> 00:03:04,640 to keep things fair. 75 00:03:04,640 --> 00:03:06,130 Now, once you're in the room, 76 00:03:06,130 --> 00:03:08,750 you're in it, you get to do these four things, 77 00:03:08,750 --> 00:03:10,753 and then you have to leave the room. 78 00:03:10,753 --> 00:03:12,400 Now, once you leave the room, 79 00:03:12,400 --> 00:03:14,330 this is what you call unlock. 80 00:03:14,330 --> 00:03:15,660 We're gonna say unlock. 81 00:03:15,660 --> 00:03:17,970 That's gonna free the scheduler at that point, 82 00:03:17,970 --> 00:03:20,970 to come in and let another goroutine in. 83 00:03:20,970 --> 00:03:25,360 Again, we only allow one goroutine in the room at a time. 84 00:03:25,360 --> 00:03:28,320 So the mutex is giving us the ability 85 00:03:28,320 --> 00:03:30,360 to create a critical section of code, 86 00:03:30,360 --> 00:03:32,720 to make sure this code executes atomically 87 00:03:32,720 --> 00:03:34,370 no matter how many instructions. 88 00:03:34,370 --> 00:03:36,140 But there's a real cost to mutexes, 89 00:03:36,140 --> 00:03:39,050 we have to be aware of this, and that's latency. 90 00:03:39,050 --> 00:03:41,370 And the more goroutines that are waiting to get in, 91 00:03:41,370 --> 00:03:44,060 and the longer this goroutine takes to get out, 92 00:03:44,060 --> 00:03:45,580 the longer the latency is. 93 00:03:45,580 --> 00:03:49,870 This is technically back pressure internally, 94 00:03:49,870 --> 00:03:51,210 inside your software. 95 00:03:51,210 --> 00:03:53,880 We're gonna wanna be able to measure this back pressure, 96 00:03:53,880 --> 00:03:55,360 we have gotta reduce it. 97 00:03:55,360 --> 00:03:59,500 You've got to make sure you do the least amount of work 98 00:03:59,500 --> 00:04:01,150 that has to be atomic. 99 00:04:01,150 --> 00:04:03,800 Because if you do more work that needs to be atomic, 100 00:04:03,800 --> 00:04:06,020 you're putting more latency or back pressure 101 00:04:06,020 --> 00:04:07,210 inside the system. 102 00:04:07,210 --> 00:04:09,320 But you've gotta make sure that it is atomic, 103 00:04:09,320 --> 00:04:11,020 I've seen lots of code 104 00:04:11,020 --> 00:04:14,270 where they've tried to cut some corners on latency, 105 00:04:14,270 --> 00:04:15,900 and then these instructions end up 106 00:04:15,900 --> 00:04:17,530 not really running atomically. 107 00:04:17,530 --> 00:04:19,490 I'll try to show you an example of that. 108 00:04:19,490 --> 00:04:21,590 This is your mutex. 109 00:04:21,590 --> 00:04:23,410 So look at what we do here. 110 00:04:23,410 --> 00:04:24,953 We've got our two goroutines, 111 00:04:24,953 --> 00:04:27,010 we've got our wait group, we add two. 112 00:04:27,010 --> 00:04:28,420 There's the two goroutines again, 113 00:04:28,420 --> 00:04:32,500 and now here's the call to lock and unlock. 114 00:04:32,500 --> 00:04:34,820 Now there's something that I do, personally, 115 00:04:34,820 --> 00:04:36,260 you're not gonna see this, probably, 116 00:04:36,260 --> 00:04:37,920 in other people's code. 117 00:04:37,920 --> 00:04:38,950 What you might have noticed 118 00:04:38,950 --> 00:04:42,200 is I've created an artificial code block 119 00:04:42,200 --> 00:04:43,490 with the curly brackets. 120 00:04:43,490 --> 00:04:46,150 It does create a new level of scope, 121 00:04:46,150 --> 00:04:47,840 but what I love about it 122 00:04:47,840 --> 00:04:50,580 is it gives us a visual in the code 123 00:04:50,580 --> 00:04:54,120 of what instructions we're trying to execute atomically. 124 00:04:54,120 --> 00:04:56,180 In other words, it's able to create the room. 125 00:04:56,180 --> 00:04:58,230 I love this, this is really important, 126 00:04:58,230 --> 00:05:00,000 because there's another rule here 127 00:05:00,000 --> 00:05:01,960 and I'm gonna put my foot down. 128 00:05:01,960 --> 00:05:04,860 The same function that calls lock 129 00:05:04,860 --> 00:05:06,710 must call unlock. 130 00:05:06,710 --> 00:05:08,250 You cannot separate these calls 131 00:05:08,250 --> 00:05:10,300 between two different functions. 132 00:05:10,300 --> 00:05:12,410 You're gonna miss a lock or an unlock, 133 00:05:12,410 --> 00:05:14,150 and you're gonna have a deadlock situation. 134 00:05:14,150 --> 00:05:16,460 Remember, if this goroutine doesn't call unlock, 135 00:05:16,460 --> 00:05:18,160 you now have a deadlock situation. 136 00:05:18,160 --> 00:05:21,570 All these goroutines are stuck because they can't get in. 137 00:05:21,570 --> 00:05:23,480 So I want the lock and the unlock 138 00:05:23,480 --> 00:05:28,420 to always be together, always be together, okay? 139 00:05:28,420 --> 00:05:30,980 And some people like to use the defer, 140 00:05:30,980 --> 00:05:32,270 now we haven't talked about defer. 141 00:05:32,270 --> 00:05:35,160 Defer says execute this piece of code 142 00:05:35,160 --> 00:05:38,440 after the function that you're inside right now returns. 143 00:05:38,440 --> 00:05:40,410 So it's, have the function do its thing, 144 00:05:40,410 --> 00:05:42,250 return, call the defer. 145 00:05:42,250 --> 00:05:45,520 I can't use a defer here, because I'm in a loop. 146 00:05:45,520 --> 00:05:47,460 But what you might see a lot 147 00:05:47,460 --> 00:05:49,780 in the beginning of a function, 148 00:05:49,780 --> 00:05:51,910 at the very beginning of a function, 149 00:05:51,910 --> 00:05:53,620 is somebody calling lock 150 00:05:53,620 --> 00:05:56,800 and deferring the unlock. 151 00:05:56,800 --> 00:05:59,340 But I would wanna see this type of code 152 00:05:59,340 --> 00:06:02,510 as like the first two lines of a function, 153 00:06:02,510 --> 00:06:04,340 that would be fine. 154 00:06:04,340 --> 00:06:05,480 But I'm inside of a loop, 155 00:06:05,480 --> 00:06:07,630 if I do this I'm gonna be in a lot of trouble, 156 00:06:07,630 --> 00:06:10,700 because Go doesn't let the same goroutine 157 00:06:10,700 --> 00:06:12,980 call lock twice. 158 00:06:12,980 --> 00:06:14,060 It's something that, again, 159 00:06:14,060 --> 00:06:15,640 Go is about keeping things simple. 160 00:06:15,640 --> 00:06:17,790 There are languages like C++, 161 00:06:17,790 --> 00:06:19,350 I've done it in C#, 162 00:06:19,350 --> 00:06:21,670 where the same goroutine, or in that case thread, 163 00:06:21,670 --> 00:06:25,070 can call lock multiple times as long as it calls unlock. 164 00:06:25,070 --> 00:06:26,450 That doesn't exist in Go. 165 00:06:26,450 --> 00:06:28,000 One lock to one unlock. 166 00:06:28,000 --> 00:06:29,100 So look what we've done. 167 00:06:29,100 --> 00:06:32,510 We've now said we're gonna lock these three lines of code, 168 00:06:32,510 --> 00:06:34,570 these three lines of code have to run atomically. 169 00:06:34,570 --> 00:06:36,410 There cannot be a context switch 170 00:06:36,410 --> 00:06:39,730 to another goroutine that also wants in this room. 171 00:06:39,730 --> 00:06:42,900 And by doing that, what we're gonna now get, 172 00:06:42,900 --> 00:06:45,210 example three, if we do that, 173 00:06:45,210 --> 00:06:46,780 and I run go build, 174 00:06:46,780 --> 00:06:48,170 now what's gonna be great 175 00:06:48,170 --> 00:06:51,540 is I now backed out to four, I have four. 176 00:06:51,540 --> 00:06:55,130 And if I put the fmt crawl in here, 177 00:06:55,130 --> 00:06:58,020 print line value, like we did before, remember. 178 00:06:58,020 --> 00:07:00,110 If I do that and then I build it, 179 00:07:00,110 --> 00:07:01,890 then I run it, 180 00:07:01,890 --> 00:07:04,500 now we're gonna still see four, 181 00:07:04,500 --> 00:07:08,037 because what we've now done with the mutex is said, 182 00:07:08,037 --> 00:07:11,427 "I don't care that we're making a system call here, 183 00:07:11,427 --> 00:07:13,087 "that is atomic now, 184 00:07:13,087 --> 00:07:14,500 "so we're gonna have to wait." 185 00:07:14,500 --> 00:07:17,700 Which just added more latency to our software 186 00:07:17,700 --> 00:07:19,510 by doing this print here, 187 00:07:19,510 --> 00:07:22,010 because we gotta wait for the system call to get service. 188 00:07:22,010 --> 00:07:23,567 This would be a case where I'm like, 189 00:07:23,567 --> 00:07:26,607 "Yo, yo, yo, yo, don't do the print inside the mutex, 190 00:07:26,607 --> 00:07:29,423 "you've just added extra latency costs here. 191 00:07:29,423 --> 00:07:32,610 "You gotta just do the bare minimum." 192 00:07:32,610 --> 00:07:33,443 The bare minimum. 193 00:07:33,443 --> 00:07:34,527 So always inside of a mutex, 194 00:07:34,527 --> 00:07:37,550 you gotta look to validate we're doing the bare minimum, 195 00:07:37,550 --> 00:07:41,660 but we're still maintaining a full level of atomic. 196 00:07:41,660 --> 00:07:42,920 Everything's still atomic. 197 00:07:42,920 --> 00:07:43,910 I can tell you this. 198 00:07:43,910 --> 00:07:45,410 If I do a code review, 199 00:07:45,410 --> 00:07:47,640 and I see something like this, which people do, 200 00:07:47,640 --> 00:07:49,540 this is a really bad smell. 201 00:07:49,540 --> 00:07:51,113 I've seen people do this. 202 00:07:51,113 --> 00:07:52,537 They say, "You know what, Bill? 203 00:07:52,537 --> 00:07:55,967 "I wanna get out of this mutex really, really quickly. 204 00:07:55,967 --> 00:07:58,017 "So I'm gonna do the following. 205 00:07:58,017 --> 00:08:01,310 "I'm gonna create a local variable out here," 206 00:08:01,310 --> 00:08:02,260 we'll have to do that. 207 00:08:02,260 --> 00:08:05,117 Value int. 208 00:08:05,117 --> 00:08:08,547 "And what I'm gonna do is go into the mutex one 209 00:08:08,547 --> 00:08:10,877 "to read the value out, Bill, 210 00:08:10,877 --> 00:08:13,947 "then what I'm gonna do is value plus plus, 211 00:08:13,947 --> 00:08:15,647 "and then, Bill, what I'm gonna do 212 00:08:15,647 --> 00:08:19,237 "is use the lock one more time 213 00:08:19,237 --> 00:08:21,697 "to write it back, and look what I've done, Bill, 214 00:08:21,697 --> 00:08:25,440 "I've made my mutex lock and unlock very, very fast." 215 00:08:25,440 --> 00:08:28,040 No, what you've done, is lost all ability 216 00:08:28,040 --> 00:08:30,210 to have these operations atomic. 217 00:08:30,210 --> 00:08:33,580 Because even though your read is atomic, guess what? 218 00:08:33,580 --> 00:08:35,100 Your write, here, isn't. 219 00:08:35,100 --> 00:08:37,310 And that means another goroutine can still come in 220 00:08:37,310 --> 00:08:39,830 and read a dirty value. 221 00:08:39,830 --> 00:08:41,780 So if I see code inside of a function 222 00:08:41,780 --> 00:08:43,940 that's using the same mutex like this, 223 00:08:43,940 --> 00:08:45,490 lock, unlock, lock, unlock, 224 00:08:45,490 --> 00:08:46,750 this is a big smell 225 00:08:46,750 --> 00:08:50,580 that we really don't have an atomic piece of code. 226 00:08:50,580 --> 00:08:52,400 These are code smells that you can find. 227 00:08:52,400 --> 00:08:55,570 So we're gonna have the one lock and unlock here, 228 00:08:55,570 --> 00:08:57,320 gotta make sure we do everything that we need 229 00:08:57,320 --> 00:08:59,218 and only what we need 230 00:08:59,218 --> 00:09:01,830 to reduce the latency costs. 231 00:09:01,830 --> 00:09:03,170 This is gonna be your death. 232 00:09:03,170 --> 00:09:04,410 Remember I told you performance 233 00:09:04,410 --> 00:09:06,870 comes from latency on networking in I/O, 234 00:09:06,870 --> 00:09:09,150 latency on that garbage collection, right? 235 00:09:09,150 --> 00:09:10,580 How much pressure we put in there. 236 00:09:10,580 --> 00:09:13,460 The next thing is how we access data, 237 00:09:13,460 --> 00:09:15,000 how we access the hardware, 238 00:09:15,000 --> 00:09:17,480 this is also gonna be a big cost, right? 239 00:09:17,480 --> 00:09:20,320 What kind of back pressure do we create 240 00:09:20,320 --> 00:09:21,750 inside of our software, 241 00:09:21,750 --> 00:09:24,310 now we're dealing with algorithm efficiency issues, 242 00:09:24,310 --> 00:09:28,160 but with latency because we can hold those locks 243 00:09:28,160 --> 00:09:29,053 way too long. 244 00:09:30,110 --> 00:09:31,490 But we've got it here. 245 00:09:31,490 --> 00:09:34,443 Now, one other thing is the read, write mutex. 246 00:09:35,340 --> 00:09:38,407 The read, write mutex says the following. 247 00:09:38,407 --> 00:09:41,517 "It is okay to read memory. 248 00:09:41,517 --> 00:09:44,087 "Reading memory doesn't cause a problem. 249 00:09:44,087 --> 00:09:46,397 "It's only on the mutation. 250 00:09:46,397 --> 00:09:49,607 "And what if you suddenly need ten goroutines 251 00:09:49,607 --> 00:09:51,427 "to read something at the same time? 252 00:09:51,427 --> 00:09:53,087 "There's no reason to synchronize that, 253 00:09:53,087 --> 00:09:55,677 "that would be a waste of latency or back pressure. 254 00:09:55,677 --> 00:09:57,857 "We only need to stop the reading 255 00:09:57,857 --> 00:09:59,270 "if we wanna write." 256 00:09:59,270 --> 00:10:02,910 This is where the read, write mutex comes in. 257 00:10:02,910 --> 00:10:05,307 So here's our read, write mutex right there. 258 00:10:05,307 --> 00:10:07,383 And what are read, write mutex does, 259 00:10:08,230 --> 00:10:11,600 is it allows us to have multiple reads 260 00:10:11,600 --> 00:10:14,770 across that one write. 261 00:10:14,770 --> 00:10:18,700 So here we are with our slice of strings, 262 00:10:18,700 --> 00:10:20,330 there it is, a slice of strings 263 00:10:20,330 --> 00:10:23,330 is our core data structure here. 264 00:10:23,330 --> 00:10:25,490 We're gonna be synchronizing, 265 00:10:25,490 --> 00:10:27,220 this is all synchronization, 266 00:10:27,220 --> 00:10:28,930 so here it is, here's our slice. 267 00:10:28,930 --> 00:10:31,970 Remember that your slice is a three word data structure, 268 00:10:31,970 --> 00:10:34,920 I'm drawing it horizontally right now, 269 00:10:34,920 --> 00:10:38,210 which is our pointer, our length, and our capacity. 270 00:10:38,210 --> 00:10:40,320 Three words, three word data structure, 271 00:10:40,320 --> 00:10:42,500 and our read, write mutex also is usable 272 00:10:42,500 --> 00:10:45,280 in its zero value state. 273 00:10:45,280 --> 00:10:47,810 Now, on line 22 we have our read counter, 274 00:10:47,810 --> 00:10:49,570 again, this is gonna be a global variable, 275 00:10:49,570 --> 00:10:52,723 to track the number of concurrent reads. 276 00:10:53,610 --> 00:10:55,570 Now, look at what we do here. 277 00:10:55,570 --> 00:10:58,720 I'm gonna go ahead and launch a single goroutine 278 00:10:58,720 --> 00:11:00,630 whose entire job it is 279 00:11:01,945 --> 00:11:05,440 to write to this three word data structure. 280 00:11:05,440 --> 00:11:07,690 In other words, it's gonna be doing an append. 281 00:11:07,690 --> 00:11:10,420 And then I have another set of goroutines, 282 00:11:10,420 --> 00:11:13,390 I've got eight goroutines, eight of 'em. 283 00:11:13,390 --> 00:11:15,030 Whatever I have here. 284 00:11:15,030 --> 00:11:17,250 And they're gonna be doing reads, 285 00:11:17,250 --> 00:11:19,510 they're gonna be reading this data structure. 286 00:11:19,510 --> 00:11:21,970 All of them can read at the same time, 287 00:11:21,970 --> 00:11:23,180 that is not a problem, 288 00:11:23,180 --> 00:11:24,830 it's the write that hurts. 289 00:11:24,830 --> 00:11:26,300 So look at what we do. 290 00:11:26,300 --> 00:11:28,160 There it is, on the lock. 291 00:11:28,160 --> 00:11:31,590 Any time we wanna do a write, we have to lock. 292 00:11:31,590 --> 00:11:32,423 Leave the lock, 293 00:11:32,423 --> 00:11:34,197 and then we're gonna do the unlock on the right. 294 00:11:34,197 --> 00:11:36,367 What this is gonna tell the scheduler is, 295 00:11:36,367 --> 00:11:41,367 "Don't let any activity go against this data structure 296 00:11:41,537 --> 00:11:43,470 "or these lines of code, in this case." 297 00:11:43,470 --> 00:11:45,650 And these lines of code are doing two things, 298 00:11:45,650 --> 00:11:47,520 they're reading the read counter 299 00:11:47,520 --> 00:11:50,940 and it's doing an append on line 71, there, 300 00:11:50,940 --> 00:11:52,810 to modify this data. 301 00:11:52,810 --> 00:11:54,390 And then what we have on the reader, 302 00:11:54,390 --> 00:11:56,100 which you've got multiple goroutines, 303 00:11:56,100 --> 00:11:58,490 is it's asking for the R lock, 304 00:11:58,490 --> 00:12:00,613 with the R unlock. 305 00:12:00,613 --> 00:12:02,477 What that is saying is, 306 00:12:02,477 --> 00:12:04,767 "Allow these goroutines to go ahead, 307 00:12:04,767 --> 00:12:07,667 "and do this length call at the same time, 308 00:12:07,667 --> 00:12:09,927 "because we know it's not modifying behind it. 309 00:12:09,927 --> 00:12:12,107 "If you ask for an R lock, you better just read. 310 00:12:12,107 --> 00:12:13,087 "You start writing here, 311 00:12:13,087 --> 00:12:15,080 "we're gonna have synchronization issues." 312 00:12:15,080 --> 00:12:17,610 But we're using the atomic instructions for this 313 00:12:17,610 --> 00:12:20,830 because eight goroutines might want to increment 314 00:12:20,830 --> 00:12:23,580 add in 64, that local variable. 315 00:12:23,580 --> 00:12:24,800 This is really nice. 316 00:12:24,800 --> 00:12:27,910 So the scheduler is doing synchronization for us. 317 00:12:27,910 --> 00:12:29,870 When we wanna write, we ask for the lock, 318 00:12:29,870 --> 00:12:31,250 and when we ask for the lock, 319 00:12:31,250 --> 00:12:32,604 then that goroutine here 320 00:12:32,604 --> 00:12:34,150 is gonna have to wait for all the read locks 321 00:12:34,150 --> 00:12:35,670 to call read unlock. 322 00:12:35,670 --> 00:12:38,140 The scheduler will not give out another read lock 323 00:12:38,140 --> 00:12:40,050 until the writing finishes. 324 00:12:40,050 --> 00:12:43,420 Once the write finishes, we can give out the read locks. 325 00:12:43,420 --> 00:12:45,790 So if we run this program here, 326 00:12:45,790 --> 00:12:47,360 example four, 327 00:12:47,360 --> 00:12:51,480 what we're gonna see on the output 328 00:12:52,870 --> 00:12:56,700 is this synchronization between reads and writes. 329 00:12:56,700 --> 00:13:00,240 So here we started off with a length of zero on the slice, 330 00:13:00,240 --> 00:13:03,170 and you can see how many goroutines are reading 331 00:13:03,170 --> 00:13:04,840 the slice length at the same time. 332 00:13:04,840 --> 00:13:06,150 There is all eight. 333 00:13:06,150 --> 00:13:09,587 But eventually, randomly here, 334 00:13:09,587 --> 00:13:11,037 the goroutine wakes up and says, 335 00:13:11,037 --> 00:13:13,047 "Hey, I wanna write," to the slice, 336 00:13:13,047 --> 00:13:14,640 "I wanna append a value." 337 00:13:14,640 --> 00:13:16,980 Notice that all the reads stop. 338 00:13:16,980 --> 00:13:18,880 We do the write, the length is now one, 339 00:13:18,880 --> 00:13:21,230 and then all the reads start up again. 340 00:13:21,230 --> 00:13:25,200 Look at the scheduler doing synchronization for us 341 00:13:25,200 --> 00:13:27,610 through the mutex, 342 00:13:27,610 --> 00:13:28,920 because every time we wanna write, 343 00:13:28,920 --> 00:13:30,210 we stop all the reads, 344 00:13:30,210 --> 00:13:32,910 we modify, we start reading again. 345 00:13:32,910 --> 00:13:35,140 It's a really beautiful data structure 346 00:13:35,140 --> 00:13:36,580 for dictionaries and things 347 00:13:36,580 --> 00:13:38,040 where you're doing multiple reads, 348 00:13:38,040 --> 00:13:40,730 and then eventually you have to stop on the write. 349 00:13:40,730 --> 00:13:42,830 So this read, write mutex 350 00:13:44,000 --> 00:13:47,100 is another powerful data structure for synchronization. 351 00:13:47,100 --> 00:13:50,220 But the read, write mutex is a little bit slower 352 00:13:50,220 --> 00:13:51,410 than your regular mutex, 353 00:13:51,410 --> 00:13:53,540 so if you truly need pure synchronization 354 00:13:54,510 --> 00:13:56,780 for everything, you gotta use this. 355 00:13:56,780 --> 00:13:59,060 If you can have multiple reads happening 356 00:13:59,060 --> 00:14:00,800 while there's no writing, 357 00:14:00,800 --> 00:14:02,970 then you wanna use that read, write mutex. 358 00:14:02,970 --> 00:14:05,670 So we've got that atomic instruction, right? 359 00:14:05,670 --> 00:14:06,640 Where we're dealing with data, 360 00:14:06,640 --> 00:14:09,160 32 to 64 bits, great for counters. 361 00:14:09,160 --> 00:14:11,690 You could do some level of signaling with that, too, 362 00:14:11,690 --> 00:14:14,480 if you really need high performance signaling. 363 00:14:14,480 --> 00:14:18,410 And you've got your mutexes to make lines of code atomic, 364 00:14:18,410 --> 00:14:21,610 and you can use the same mutex across an API set, too. 365 00:14:21,610 --> 00:14:25,140 So you can have multiple instruction sets 366 00:14:25,140 --> 00:14:27,180 across a program using the same mutex. 367 00:14:27,180 --> 00:14:29,860 But remember, mutexes create latency, 368 00:14:29,860 --> 00:14:31,610 latency is death, 369 00:14:31,610 --> 00:14:32,950 so we've gotta really make sure 370 00:14:32,950 --> 00:14:36,963 that we're doing less than more inside those mutexes.