字幕記錄 00:02 today I'd like to talk about NGO which 00:07 is interesting especially interesting 00:08 for us in this course because course NGO 00:10 is the language at the labs you're all 00:12 going to do the labs in and so I want to 00:14 focus today particularly on some of the 00:17 machinery that sort of most useful in 00:19 the labs and in most particular to 00:22 distributed programming um first of all 00:26 you know it's worth asking why we use go 00:29 in this class in fact we could have used 00:32 any one of a number of other system 00:33 style languages plenty languages like 00:35 Java or C sharp or even Python that 00:38 provide the kind of facilities we need 00:40 and indeed we used to use C++ in this 00:42 class and it worked out fine it'll go 00:47 indeed like many other languages 00:49 provides a bunch of features which are 00:50 particularly convenient 00:51 that's good support for threads and 00:53 locking and synchronization between 00:56 threads which we use a lot it is a 00:59 convenient remote procedure call package 01:01 which doesn't sound like much but it 01:04 actually turns out to be a significant 01:06 constraint from in languages like C++ 01:09 for example it's actually a bit hard to 01:11 find a convenient easy to use remote 01:13 procedure call package and of course we 01:14 use it all the time in this course or 01:16 programs and different machines to talk 01:18 to each other unlike C++ go is type safe 01:22 and memory safe that is it's pretty hard 01:25 to write a program that due to a bug 01:27 scribbles over some random piece of 01:29 memory and then causes the program to do 01:31 mysterious things and that just 01:34 eliminates a big class of bugs similarly 01:36 it's garbage collected which means you 01:39 never in danger of priam the same memory 01:41 twice or free memory that's still in use 01:44 or something the garbage vector just 01:46 frees things when they stop being used 01:48 and one thing it's maybe not obvious 01:51 until you played around with just this 01:54 kind of programming before but the 01:56 combination of threads and garbage 01:58 collection is particularly important one 02:01 of the things that goes wrong in a non 02:03 garbage collected language like C++ if 02:06 you use threads is that it's always a 02:08 bit of a puzzle and requires a bunch of 02:10 bookkeeping to figure out when the last 02:13 thread 02:14 that's using a shared object has 02:15 finished using that object because only 02:17 then can you free the object as you end 02:19 up writing quite a bit of coat it's like 02:20 manually the programmer it's about a 02:22 bunch of code to manually you know do 02:24 reference counting or something in order 02:26 to figure out you know when the last 02:28 thread stopped using an object and 02:30 that's just a pain and that problem 02:32 completely goes away if you use garbage 02:34 collection like we haven't go 02:36 and finally the language is simple much 02:39 simpler than C++ one of the problems 02:41 with using C++ is that often if you made 02:44 an error you know maybe even just a typo 02:47 the the error message you get back from 02:51 the compiler is so complicated that in 02:53 C++ it's usually not worth trying to 02:56 figure out what the error message meant 02:57 and I find it's always just much quicker 02:59 to go look at the line number and try to 03:01 guess what the error must have been 03:02 because the language is far too 03:04 complicated 03:04 whereas go is you know probably doesn't 03:07 have a lot of people's favorite features 03:09 but it's relatively straightforward 03:11 language okay so at this point you're 03:14 both on the tutorial if you're looking 03:17 for sort of you know what to look at 03:19 next to learn about the language a good 03:21 place to look is the document titled 03:23 effective go which you know you can find 03:25 by searching the web all right the first 03:30 thing I want to talk about is threads 03:33 the reason why we care a lot about 03:35 threads in this course is that threads 03:39 are the sort of main tool we're going to 03:41 be using to manage concurrency in 03:44 programs and concurrency is a particular 03:47 interest in distributed programming 03:49 because it's often the case that one 03:52 program actually needs to talk to a 03:53 bunch of other computers you know client 03:55 may talk to many servers or a server may 03:58 be serving requests at the same time on 04:00 behalf of many different clients and so 04:02 we need a way to say oh you know I'm my 04:04 program really has seven different 04:06 things going on because it's talking to 04:07 seven different clients and I want a 04:10 simple way to allow it to do these seven 04:12 different things you know without too 04:14 much complex programming I mean sort of 04:16 thrust threads are the answer so these 04:19 are the things that the go documentation 04:21 calls go routines which I call threads 04:24 they're go routines are really this same 04:26 as what everybody else calls 04:27 Red's so the way to think of threads is 04:32 that you have a program of one program 04:36 and one address space I'm gonna draw a 04:43 box to sort of denote an address space 04:46 and within that address space in a 04:48 serial program without threads you just 04:51 have one thread of execution executing 04:54 code in that address space one program 04:57 counter one set of registers one stack 05:00 that are sort of describing the current 05:02 state of the execution in a threaded 05:04 program like a go program you could have 05:06 multiple threads and you know I got raw 05:09 it as multiple squiggly lines and when 05:10 each line represents really is a 05:13 separate if the especially if the 05:16 threads are executing at the same time 05:17 but a separate program counter a 05:19 separate set of registers and a separate 05:21 stack for each of the threads so that 05:24 they can have a sort of their own thread 05:26 of control and be executing each thread 05:28 in a different part of the program and 05:31 so hidden here is that for every stack 05:33 now there's a syrupy thread there's a 05:35 stack that it's executing on the stacks 05:41 are actually in in the one address space 05:44 of the program so even though each stack 05:46 each thread has its own stack 05:47 technically the they're all in the same 05:51 address space and different threads 05:52 could refer to each other stacks if they 05:53 knew the right addresses although you 05:55 typically don't do that and then go when 05:59 you even the main program you know when 06:01 you first start up the program and it 06:02 runs in main that's also it's just a go 06:05 routine and can do all the things that 06:06 go teens can do all right so as I 06:14 mentioned one of the big reasons is to 06:17 allow different parts of the program to 06:21 sort of be in its own point in in a 06:25 different activity so I usually refer to 06:27 that as IO concurrency for historical 06:31 reasons and the reason I call it IO 06:36 concurrency is that in the old days 06:38 where this first came up is that oh you 06:39 might have one thread that's waiting to 06:41 read 06:41 from the disk and while it's waiting to 06:43 reach from the disk you'd like to have a 06:44 second thread that maybe can compute or 06:46 read somewhere else in the disk or send 06:49 a message in the network and wait for 06:50 reply so and so I open currencies one of 06:54 the things that threads by you for us it 06:57 would usually mean I can I open currency 07:00 we usually mean I can have one program 07:01 that has launched or removed procedure 07:04 calls requests to different servers on 07:06 the network and is waiting for many 07:08 replies at the same time that's how 07:10 it'll come up for us and you know the 07:13 way you would do that with threads is 07:15 that you would create one thread for 07:17 each of the remote procedure calls that 07:18 you wanted to launch that thread would 07:21 have code that you know sent the remote 07:23 procedure call request message and sort 07:26 of waited at this point in the thread 07:27 and then finally when the reply came 07:29 back the thread would continue executing 07:31 and using threads allows us to have 07:33 multiple threads that all launch 07:34 requests into the network at the same 07:36 time they all wait or they don't have to 07:38 do it at the same time they can you know 07:40 execute the different parts of this 07:41 whenever they feel like it 07:43 so that's i/o concurrency sort of 07:45 overlapping of the progress of different 07:49 activities and allowing one activity is 07:53 waiting other activities can proceed 07:57 another big reason to use threads is 08:00 multi-core parallelism which I'll just 08:02 call parallelism and here the thing 08:08 where we'd be trying to achieve with 08:10 threads is if you have a multi-core 08:11 machine like I'm sure all of you do in 08:13 your laptops if you have a sort of 08:15 compute heavy job that needs a lot of 08:17 CPU cycles wouldn't it be nice if you 08:19 could have one program that could use 08:21 CPU cycles on all of the cores of the 08:23 machine and indeed if you write a 08:25 multi-threaded go if you launch multiple 08:27 go routines and go and they do something 08:30 computer intensive like sit there in a 08:31 loop and you know compute digits of pi 08:33 or something then up to the limit of the 08:36 number of cores in the physical machine 08:38 your threads will run truly in parallel 08:41 and if you launch you know two threads 08:43 instead of one you'll get twice as many 08:45 you'll be able to use twice as many CPU 08:48 cycles per second so this is very 08:51 important to some people it's not a big 08:53 deal on this course 08:54 be it's rare that we'll sort of think 08:57 specifically about this kind of 08:59 parallelism in the real world though of 09:01 building things like servers to form 09:05 parts of the distributed systems it can 09:06 sometimes be extremely important to be 09:09 able to have the server be able to run 09:11 threads and harness the CPU power of a 09:13 lot of cores just because the load from 09:15 clients can often be pretty high okay so 09:18 parallelism is a second reason why 09:22 threads are quite a bit interested in 09:25 distributed systems and a third reason 09:27 which is maybe a little bit less 09:29 important is there's some there's times 09:32 when you really just want to be able to 09:35 do something in the background or you 09:38 know there's just something you need to 09:39 do periodically and you don't want to 09:42 have to sort of in the main part of your 09:45 program sort of insert checks to say 09:47 well should I be doing this things that 09:49 should happen every second or so you 09:51 just like to be able to fire something 09:52 up that every second does whatever the 09:54 periodic thing is so there's some 09:56 convenience reasons and an example which 10:00 will come up for you is it's often the 10:03 case that some you know a master server 10:05 may want to check periodically whether 10:07 its workers are still alive because one 10:09 of them is died you know you want to 10:10 launch that work on another machine like 10:12 MapReduce might do that and one way to 10:14 arrange sort of oh do this check every 10:17 second every minute you know send a 10:19 message to the worker are you alive is 10:21 to fire off a go routine that just sits 10:24 in a loop that sleeps for a second and 10:25 then does the periodic thing and then 10:26 sleeps for a second again and so in the 10:28 labs you'll end up firing off these kind 10:31 of threads quite a bit yes is the 10:36 overhead worth it yes the overhead is 10:42 really pretty small for this stuff I 10:44 mean you know it depends on how many you 10:46 create a million threads that he sit in 10:50 a loop waiting for a millisecond and 10:52 then send a network message that's 10:53 probably a huge load on your machine but 10:56 if you create you know ten threads that 10:59 sleep for a second and do a little bit 11:01 of work it's probably not a big deal at 11:04 all and it's 11:06 I guarantee you the programmer time you 11:10 say by not having to sort of mush 11:13 together they're different different 11:16 activities into one line of code it's 11:19 it's worth the small amount of CPU cost 11:21 almost always still you know you will if 11:26 you're unlucky you'll discover in the 11:27 labs that some loop of yours is not 11:30 sleeping long enough or are you fired 11:32 off a bunch of these and never made them 11:35 exit for example and they just 11:37 accumulate so you can push it too far 11:41 okay so these are the reasons that the 11:43 main reasons that people like threads a 11:46 lot and that will use threads in this 11:47 class any other questions about threads 11:50 in general by asynchronous program you 12:01 mean like a single thread of control 12:03 that keeps state about many different 12:06 activities yeah so this is a good 12:09 question actually there is you know what 12:12 would happen if we didn't have threads 12:13 or we'd for some reason we didn't want 12:15 to use threats like how would we be able 12:16 to write a program that could you know a 12:18 server that could talk to many different 12:21 clients at the same time or a client 12:23 that could talk to him any servers right 12:24 what what tools could be used and it 12:26 turns out there is sort of another line 12:29 of another kind of another major style 12:36 of how do you structure these programs 12:37 called 12:38 you call the asynchronous program I 12:40 might call it a vent driven programming 12:42 so sort of or you could use a vent 12:45 prevent programming and the the general 12:50 structure of an event-driven program is 12:52 usually that it has a single thread and 12:54 a single loop and what that loop does is 12:57 sits there and waits for any input or 13:01 sort of any event that might trigger 13:03 processing so an event might be the 13:05 arrival of a request from a client or a 13:07 timer going off or if you're building a 13:10 Window System protect many Windows 13:12 systems on your laptops I've driven 13:14 written an event-driven style where what 13:16 they're waiting for is like key clicks 13:17 or Mouse move 13:18 or something so you might have a single 13:20 in an event-driven program it of a 13:21 single threat of control sits an aloof 13:23 waits for input and whenever it gets an 13:25 input like a packet it figures out oh 13:27 you know which client did this packet 13:28 come from and then it'll have a table of 13:31 sort of what the state is of whatever 13:34 activity its managing for that client 13:38 and it'll say oh gosh I was in the 13:40 middle of reading such-and-such a file 13:42 you know now it's asked me to read the 13:44 next block I'll go and be the next block 13:45 and return it and threats are generally 13:55 more convenient because they allow you 13:57 to really you know it's much easier to 14:00 write sequential just like straight 14:01 lines of control code that does you know 14:03 computes sends a message waits for 14:05 response whatever it's much easier to 14:07 write that kind of code in a thread than 14:09 it is to chop up whatever the activity 14:13 is into a bunch of little pieces that 14:16 can sort of be activated one at a time 14:19 by one of these event-driven loops that 14:23 said the well and so one problem with 14:29 the scheme is that it's it's a little 14:31 bit of a pain to program another 14:32 potential defect is that while you get 14:34 io concurrency from this approach you 14:36 don't get CPU parallelism so if you're 14:38 writing a busy server that would really 14:39 like to keep you know 32 cores busy on a 14:42 big server machine you know a single 14:45 loop is you know it's it's not a very 14:49 natural way to harness more than one 14:50 core on the other hand the overheads of 14:55 adventure and programming are generally 14:56 quite a bit less than threads you know 14:59 Ed's are pretty cheap but each one of 15:01 these threads is sitting on a stack you 15:05 know stack is a kilobyte or a kilobytes 15:07 or something you know if you have 20 of 15:09 these threads who cares if you have a 15:10 million of these threads then it's 15:12 starting to be a huge amount of memory 15:14 and you know maybe the scheduling 15:17 bookkeeping for deciding what the thread 15:19 to run next might also start you know 15:20 you now have list scheduling lists with 15:23 a thousand threads in them the threads 15:25 can start to get quite expensive so if 15:28 you are in a position where you need to 15:30 have a single server that sir 15:31 you know a million clients and has to 15:33 sort of keep a little bit of state for 15:35 each of a million clients this could be 15:37 expensive 15:39 and it's easier to write a very you know 15:43 at some expense in programmer time it's 15:46 easier to write a really stripped-down 15:47 efficient low overhead service in a 15:50 venture than programming just a lot more 15:51 work are you asking my JavaScript I 16:15 don't know the question is whether 16:18 JavaScript has multiple cores executing 16:20 your does anybody know depends on the 16:25 implementation yeah so I don't know I 16:27 mean it's a natural thought though even 16:29 in you know even an NGO you might well 16:31 want to have if you knew your machine 16:33 had eight cores if you wanted to write 16:35 the world's most efficient whatever 16:37 server you could fire up eight threads 16:39 and on each of the threads run sort of 16:42 stripped-down event-driven loop just you 16:47 know sort of one event loop Recor and 16:49 that you know that would be a way to get 16:51 both parallelism and to the bio 16:54 concurrency yes 17:05 okay so the question is what's the 17:06 difference between threads and processes 17:07 so usually on a like a UNIX machine a 17:11 process is a single program that you're 17:14 running and a sort of single address 17:16 space a single bunch of memory for the 17:18 process and inside a process you might 17:22 have multiple threads and when you ready 17:23 to go program and you run the go program 17:25 running the go program creates one unix 17:28 process and one sort of memory area and 17:31 then when your go process creates go 17:35 routines those are so sitting inside 17:37 that one process so I'm not sure that's 17:40 really an answer but just historically 17:42 the operating systems have provided like 17:45 this big box is the process that's 17:47 implemented by the operating system and 17:49 the individual and some of the operating 17:52 system does not care what happens inside 17:53 your process what language you use none 17:56 of the operating systems business but 17:59 inside that process you can run lots of 18:00 threads now you know if you run more 18:03 than one process in your machine you 18:05 know you run more than one program I can 18:06 edit or compiler the operating system 18:09 keep quite separate right you're your 18:12 editor and your compiler each have 18:13 memory but it's not the same memory that 18:15 are not allowed to look at each other 18:16 memory there's not much interaction 18:18 between different processes so you 18:20 redditor may have threads and your 18:22 compiler may have threads but they're 18:24 just in different worlds so within any 18:27 one program the threads can share memory 18:29 and can synchronize with channels and 18:31 use mutexes and stuff but between 18:33 processes there's just no no interaction 18:38 that's just a traditional structure of 18:41 these this kind of software 18:45 yeah 18:53 so the question is when a context switch 18:55 happens does it happened for all threads 19:08 okay so let's let's imagine you have a 19:10 single core machine that's really only 19:12 running and as just doing one thing at a 19:14 time maybe the right way to think about 19:19 it is that you're going to be you're 19:21 running multiple processes on your 19:22 machine the operating system will give 19:27 the CPU sort of time slicing back and 19:31 forth between these two programs so when 19:33 the hardware timer ticks and the 19:35 operating systems decides it's time to 19:37 take away the CPU from the currently 19:38 running process and give it to another 19:40 process that's done at a process level 19:48 it's complicated all right let me let me 19:52 let me restart this these the threads 19:55 that we use are based on threads that 19:57 are provided by the operating system in 20:00 the end and when the outer needs to some 20:02 context switches its switching between 20:03 the threads that it knows about so in a 20:06 situation like this the operating system 20:08 might know that there are two threads 20:09 here in this process and three threads 20:11 in this process and when the timer ticks 20:13 the operating system will based on some 20:15 scheduling algorithm pick a different 20:16 thread to run it might be a different 20:18 thread in this process or one of the 20:21 threads in this process 20:22 in addition go cleverly multiplex as 20:25 many go routines on top of single 20:27 operating system threads to reduce 20:29 overhead so it's really probably a two 20:32 stages of scheduling the operating 20:34 system picks which big thread to run and 20:37 then within that process go may have a 20:40 choice of go routines to run 20:45 all right okay so threads are convenient 20:53 because a lot of times they allow you to 20:55 write the code for each thread just as 20:57 if it were a pretty ordinary sequential 20:59 program however there are in fact some 21:04 challenges with writing threaded code 21:15 one is what to do about shared data one 21:17 of the really cool things about the 21:18 threading model is that these threads 21:20 share the same address space they share 21:22 memory if one thread creates an object 21:24 in memory you can let other threads use 21:26 it right you can have a array or 21:29 something that all the different threads 21:30 are reading and writing and that 21:31 sometimes critical right if you you know 21:33 if you're keeping some interesting state 21:35 you know maybe you have a cache of 21:36 things that your server your cache and 21:39 memory when a thread is handling a 21:40 client request it's gonna first look in 21:42 that cache but the shared cache and each 21:43 thread reads it and the threads may 21:45 write the cache to update it when they 21:48 have new information to stick in the 21:49 cache so it's really cool you can share 21:51 that memory but it turns out that it's 21:55 very very easy to get bugs if you're not 21:57 careful and you're sharing memory 21:59 between threads so a totally classic 22:02 example is you know supposing your 22:05 thread so you have a global variable N 22:07 and that's shared among the different 22:09 threads and a thread just wants to 22:11 increment n right but itself this is 22:17 likely to be an invitation to bugs right 22:20 if you don't do anything special around 22:22 this code and the reason is that you 22:25 know whenever you write code in a thread 22:27 that you you know is accessing reading 22:29 or writing data that's shared with other 22:31 threads you know there's always the 22:33 possibility and you got to keep in mind 22:35 that some other thread may be looking at 22:36 the data or modifying the data at the 22:39 same time so the obvious problem with 22:41 this is that maybe thread 1 is executing 22:43 this code and thread 2 is actually in 22:46 the same function in a different thread 22:47 executing the very same code right and 22:50 remember I'm imagining the N is a global 22:53 variable so they're talking about the 22:54 same n so what this boils down to you 22:57 know you're not actually running this 22:58 code you're running 22:59 machine code the compiler produced and 23:01 what that machine code does is it you 23:03 know it loads X into a register 23:06 you know adds one to the register and 23:13 then stores that register into X with 23:18 where X is a address of some location 23:21 and ran so you know you can count on 23:23 both of the threads 23:24 they're both executing this line of code 23:25 you know they both load the variable X 23:28 into a register effect starts out at 0 23:31 that means they both load at 0 23:32 they both increment that register so 23:33 they get one and they both store one 23:35 back to memory and now two threads of 23:37 incremented n and the resulting value is 23:39 1 which well who knows what the 23:44 programmer intended maybe that's what 23:45 the programmer wanted but chances are 23:47 not right chances are the programmer 23:49 wanted to not 1 some some instructions 24:02 are atomic so the question is a very 24:04 good question which it's whether 24:09 individual instructions are atomic so 24:11 the answer is some are and some aren't 24:13 so a store a 32-bit store is likely the 24:20 extremely likely to be atomic in the 24:23 sense that if 2 processors store at the 24:25 same time to the same memory address 24:27 32-bit values well you'll end up with is 24:30 either the 32 bits from one processor or 24:33 the 32 bits from the other processor but 24:35 not a mixture other sizes it's not so 24:38 clear like one byte stores it depends on 24:40 the CPU you using because a one byte 24:41 store is really almost certainly a 32 24:44 byte load and then a modification of 8 24:47 bits and a 32 byte store but it depends 24:50 on the processor and more complicated 24:51 instructions like increment your 24:54 microprocessor may well have an 24:55 increment instruction that can directly 24:57 increment some memory location like 25:00 pretty unlikely to be atomic although 25:04 there's atomic versions of some of these 25:05 instructions 25:07 so there's no way all right so this is 25:12 this is a just classic danger and it's 25:16 usually called a race I'm gonna come up 25:20 a lot is you're gonna do a lot of 25:22 threaded programming with shared state 25:25 race I think refers to as some ancient 25:27 class of bugs involving electronic 25:30 circuits but for us that you know the 25:33 reason why it's called a race is because 25:35 if one of the CPUs have started 25:37 executing this code and the other one 25:43 the others thread is sort of getting 25:44 close to this code it's sort of a race 25:46 as to whether the first processor can 25:48 finish and get to the store before the 25:50 second processor start status execute 25:53 the load if the first processor actually 25:54 manages it to do the store before the 25:57 second processor gets to the load then 25:59 the second processor will see the stored 26:00 value and the second processor will load 26:03 one and add one to it in store two 26:07 that's how you can justify this 26:10 terminology okay and so the way you 26:13 solve this certainly something this 26:15 simple is you insert locks 26:16 you know you as a programmer you have 26:19 some strategy in mind for locking the 26:23 data you can say well you know this 26:26 piece of shared data can only be used 26:28 when such-and-such a lock is held and 26:31 you'll see this and you may have used 26:33 this in the tutorial the go calls locks 26:36 mutexes so what you'll see is a mule Ock 26:39 before a sequence of code that uses 26:44 shared data and you unlock afterwards 26:48 and then whichever two threads execute 26:52 this when it to everyone is lucky enough 26:53 to get the lock first gets to do all 26:56 this stuff and finish before the other 26:57 one is allowed to proceed and so you can 26:59 think of wrapping a some code in a lock 27:02 as making a bunch of you know remember 27:05 this even though it's one line it's 27:07 really three distinct operations you can 27:10 think of a lock as causing this sort of 27:13 multi-step code sequence to be atomic 27:16 with respect to other people who have to 27:18 lock yes 27:26 should you can you repeat the 27:30 question 27:30 oh that's a great question the question 27:37 was how does go know which variable 27:39 we're walking right here of course is 27:41 only one variable but maybe we're saying 27:43 an equals x plus y really threes few 27:45 different variables and the answer is 27:47 that go has no idea it's not there's no 27:52 Association at all 27:55 anywhere between this lock so this new 27:58 thing is a variable which is a tight 28:00 mutex there's just there's no 28:04 association in the language between the 28:07 lock and any variables the associations 28:10 in the programmers head so as a 28:12 programmer you need to say oh here's a 28:14 bunch of shared data and any time you 28:18 modify any of it you know here's a 28:20 complex data structure say a tree or an 28:22 expandable hash table or something 28:24 anytime you're going to modify it and of 28:26 course a tree is composed many many 28:27 objects anytime you got to modify 28:29 anything that's associated with this 28:30 data structure you have to hold such and 28:32 such a lock right and of course is many 28:34 objects and instead of objects changes 28:36 because you might allocate new tree 28:37 nodes but it's really the programmer who 28:40 sort of works out a strategy for 28:41 ensuring that the data structure is used 28:44 by only one core at a time and so it 28:47 creates the one or maybe more locks and 28:50 there's many many locking strategies you 28:51 could apply to a tree you can imagine a 28:53 tree with a lock for every tree node the 28:57 programmer works out the strategy 28:58 allocates the locks and keeps in the 29:00 programmers head the relationship to the 29:02 data but go for go it's this is this 29:04 lock it's just like a very simple thing 29:07 there's a lock object the first thread 29:10 that calls lock gets the lock other 29:12 threads have to wait until none locks 29:14 and that's all go knows 29:18 yeah 29:23 does it not lock all variables that are 29:26 part of the object go doesn't know 29:29 anything about the relationship between 29:30 variables and locks so when you acquire 29:33 that lock when you have code that calls 29:37 lock exactly what it is doing it is 29:41 acquiring this lock and that's all this 29:44 does and anybody else who tries to lock 29:47 objects so somewhere else who would have 29:49 declared you know mutex knew all right 29:55 and this mu refers to some particular 29:56 lock object no and there me many many 29:58 locks right all this does is acquires 30:01 this lock and anybody else who wants to 30:04 acquire it has to wait until we unlock 30:06 this lock that's totally up to us as 30:09 programmers what we were protecting with 30:11 that lock so the question is is it 30:33 better to have the lock be a private the 30:38 private business of the data structure 30:39 like supposing it a zoning map yeah and 30:42 you know you would hope although it's 30:44 not true that map internally would have 30:46 a lock protecting it and that's a 30:49 reasonable strategy would be to have I 30:54 mean what would be to have it if you 30:56 define a data structure that needs to be 30:58 locked to have the lock be sort of 30:59 interior that have each of the data 31:01 structures methods be responsible for 31:03 acquiring that lock and the user the 31:04 data structure may never know that 31:06 that's pretty reasonable and the only 31:09 point at which that breaks down is that 31:10 um well it's a couple things one is if 31:15 the programmer knew that the data was 31:18 never shared they might be bummed that 31:20 they were paying the lock overhead for 31:22 something they knew didn't need to be 31:23 locked so that's one potential problem 31:25 the other is that if you if there's any 31:31 inter data structure of dependencies so 31:33 we have two data structures each with 31:35 locks and 31:36 and they maybe use each other then 31:38 there's a risk of cycles and deadlocks 31:41 right and the deadlocks can be solved 31:45 but the usual solutions to deadlocks 31:47 requires lifting the locks out of out of 31:52 the implementations up into the calling 31:54 code I will talk about that some point 31:56 but it's not a it's a good idea to hide 31:59 the locks but it's not always a good 32:00 idea all right okay so one problem you 32:11 run into with threads is these races and 32:14 generally you solve them with locks okay 32:16 or actually there's two big strategies 32:18 one is you figure out some locking 32:19 strategy for making access to the data 32:22 single thread one thread at a time or 32:25 yury you fix your code to not share data 32:29 if you can do that it's that's probably 32:32 better because it's less complex all 32:35 right so another issue that shows up 32:38 with leads threads is called 32:40 coordination when we're doing locking 32:44 the different threads involved probably 32:46 have no idea that the other ones exist 32:48 they just want to like be able to get 32:49 out the data without anybody else 32:51 interfering but there are also cases 32:53 where you need where you do 32:55 intentionally want different threads to 32:56 interact I want to wait for you 32:58 maybe you're producing some data you 32:59 know you're a different thread than me 33:01 you're you're producing data I'm gonna 33:03 wait until you've generated the data 33:05 before I read it right or you launch a 33:09 bunch of threads to say you crawl the 33:11 web and you want to wait for all those 33:12 fits to finish so there's times when we 33:14 intentionally want different to us to 33:16 interact with each other to wait for 33:18 each other 33:18 and that's usually called coordination 33:23 and there's a bunch of as you probably 33:26 know from having done the tutorial 33:28 there's a bunch of techniques in go for 33:31 doing this like channels 33:34 which are really about sending data from 33:37 one threat to another and breeding but 33:38 they did to be sent there's also other 33:42 stuff that more special purpose things 33:45 like there's a idea called condition 33:48 variables which is great if there's some 33:51 thread out there and you want to kick it 33:53 period you're not sure if the other 33:54 threads even waiting for you but if it 33:55 is waiting for you you just like to give 33:57 it a kick so it can well know that it 33:59 should continue whatever it's doing and 34:01 then there's wait group which is 34:06 particularly good for launching a a 34:08 known number of go routines and then 34:10 waiting for them Dolph to finish and a 34:14 final piece of damage that comes up with 34:16 threads deadlock the deadlock refers to 34:23 the general problem that you sometimes 34:25 run into where one thread 34:29 you know thread this thread is waiting 34:32 for thread two to produce something so 34:35 you know it's draw an arrow to say 34:37 thread one is waiting for thread two you 34:41 know for example thread one may be 34:42 waiting for thread two to release a lock 34:43 or to send something on the channel or 34:46 to you know decrement something in a 34:48 wait group however unfortunately maybe T 34:51 two is waiting for thread thread one to 34:55 do something and this is particularly 34:57 common in the case of locks its thread 35:00 one acquires lock a and thread to 35:01 acquire lock be so thread one is 35:05 acquired lock a throw two is required 35:07 lot B and then next thread one needs to 35:11 lock B also that is hold two locks which 35:14 sometimes shows up and it just so 35:15 happens that thread two needs to hold 35:17 block hey that's a deadlock all right at 35:19 least grab their first lock and then 35:21 proceed down to where they need their 35:23 second lock and now they're waiting for 35:24 each other forever right neither can 35:26 proceed neither then can release the 35:28 lock and usually just nothing happens so 35:33 if your program just kind of grinds to a 35:36 halt and doesn't seem to be doing 35:37 anything but didn't crash deadlock is 35:40 it's one thing to check 35:43 okay all right let's look at the web 35:48 crawler from the tutorial as an example 35:53 of some of this threading stuff I have a 36:00 couple of two solutions and different 36:03 styles are really three solutions in 36:07 different styles to allow us to talk a 36:09 bit about the details of some of this 36:10 thread programming so first of all you 36:13 all probably know web crawler its job is 36:16 you give it the URL of a page that it 36:18 starts at and you know many web pages 36:20 have links to other pages so what a web 36:23 crawler is trying to do is if that's the 36:24 first page extract all the URLs that 36:27 were mentioned that pages links you know 36:29 fetch the pages they point to look at 36:32 all those pages for the ules are all 36:33 those but all urls that they refer to 36:35 and keep on going until it's fetched all 36:38 the pages in the web let's just say and 36:41 then it should stop in addition the the 36:45 graph of pages and URLs is cyclic that 36:52 is if you're not careful 36:53 um you may end up following if you don't 36:55 remember oh I've already fetched this 36:57 web page already you may end up 36:59 following cycles forever and you know 37:01 your crawler will never finish so one of 37:03 the jobs of the crawler is to remember 37:05 the set of pages that is already crawled 37:08 or already even started a fetch for and 37:10 to not start a second fetch for any page 37:15 that it's already started fetching on 37:17 and you can think of that as sort of 37:18 imposing a tree structure finding a sort 37:21 of tree shaped subset of the cyclic 37:25 graph of actual web pages okay so we 37:31 want to avoid cycles we want to be able 37:33 to not fetch a page twice it also it 37:37 turns out that it just takes a long time 37:38 to fetch a web page but it's good 37:40 servers are slow and because the network 37:42 has a long speed of light latency and so 37:46 you definitely don't want to fetch pages 37:48 one at a time unless you want to crawl 37:50 to take many years so it pays enormous 37:54 lead to fetch many pages that same 37:56 I'm up to some limit right you want to 37:57 keep on increasing the number of pages 37:59 you fetch in parallel until the 38:01 throughput you're getting in pages per 38:03 second stops increasing that is running 38:05 increase the concurrency until you run 38:07 out of network capacity so we want to be 38:11 able to launch multiple fetches in 38:12 parallel and a final challenge which is 38:15 sometimes the hardest thing to solve is 38:17 to know when the crawl is finished 38:19 and once we've crawled all the pages we 38:21 want to stop and say we're done but we 38:24 actually need to write the code to 38:25 realize aha 38:27 we've crawled every single page and for 38:29 some solutions I've tried figuring out 38:32 when you're done has turned out to be 38:33 the hardest part all right so my first 38:38 crawler is this serial crawler here and 38:40 by the way this code is available on the 38:43 website under crawler go on the schedule 38:45 you won't look at it this wrist calls a 38:48 serial crawler it effectively performs a 38:53 depth-first search into the web graph 38:58 and there is sort of one moderately 39:02 interesting thing about it it keeps this 39:04 map called fetched which is basically 39:06 using as a set in order to remember 39:08 which pages it's crawled and that's like 39:11 the only interesting part of this you 39:12 give it a URL that at line 18 if it's 39:16 already fetched the URL it just returns 39:17 if it doesn't fetch the URL it first 39:20 remembers that it is now fetched it 39:22 actually gets fetches that page and 39:26 extracts the URLs that are in the page 39:27 with the fetcher and then iterates over 39:29 the URLs in that page and calls itself 39:33 for every one of those pages and it 39:35 passes to itself the way it it really 39:38 has just a one table there's only one 39:40 fetched map of course because you know 39:43 when I call recursive crawl and it 39:45 fetches a bunch of pages after it 39:47 returns I want to be where you know the 39:49 outer crawl instance needs to be aware 39:52 that certain pages are already fetched 39:53 so we depend very much on the fetched 39:56 map being passed between the functions 39:58 by reference instead of by copying so it 40:01 so under the hood what must really be 40:03 going on here is that go is passing a 40:05 pointer to the map object 40:08 to each of the calls of crawl so they 40:10 all share the pointer to the same object 40:12 and memory rather than copying rather 40:15 than copying than that any questions so 40:22 this code definitely does not solve the 40:24 problem that was posed right because it 40:25 doesn't launch parallel parallel fetches 40:30 now so clue we need to insert goroutines 40:33 somewhere in this code right to get 40:35 parallel fetches so let's suppose just 40:37 for chuckles dad we just start with the 40:41 most lazy thing because why so I'm gonna 40:51 just modify the code to run the 40:54 subsidiary crawls each in its own go 40:57 routine actually before I do that why 41:00 don't I run the code just to show you 41:01 what correct output looks like so hoping 41:04 this other window Emad run the crawler 41:07 it actually runs all three copies of the 41:09 crawler and they all find exactly the 41:10 same set of webpages so this is the 41:14 output that we're hoping to see five 41:16 lines five different web pages are are 41:19 fetched prints a line for each one so 41:20 let me now run the subsidiary crawls in 41:26 their own go routines and run that code 41:28 so what am I going to see the hope is to 41:35 fetch these webpages in parallel for 41:37 higher performance so okay so you're 41:42 voting for only seeing one URL and why 41:45 so why is that 41:50 yeah yes that's exactly right you know 41:55 after the after it's not gonna wait in 41:59 this loop at line 26 it's gonna zip 42:00 right through that loop I was gonna 42:02 fetch 1p when the ferry first webpage at 42:04 line 22 and then a loop it's gonna fly 42:07 off the girl routines and immediately 42:08 the scroll function is gonna return and 42:10 if it was called from main main what was 42:11 exit almost certainly before any of the 42:13 routines was able to do any work at all 42:15 so we'll probably just see the first web 42:16 page and I'm gonna do when I run it 42:19 you'll see here under serial that only 42:23 the one web page was found now in fact 42:26 since this program doesn't exit after 42:28 the serial crawler those Guru T's are 42:30 still running and they actually print 42:32 their output down here interleaved with 42:35 the next crawler example but 42:37 nevertheless the codes just adding a go 42:42 here absolutely doesn't work so let's 42:45 get rid of that okay so now I want to 42:49 show you a one style of concurrent 42:52 crawler and I'm presenting to one of 42:55 them written with shared data shared 42:59 objects and locks it's the first one and 43:02 another one written without shared data 43:05 but with passing information along 43:08 channels in order to coordinate the 43:11 different threads so this is the shared 43:12 data one or this is just one of many 43:17 ways of building a web crawler using 43:18 shared data so this code significantly 43:22 more complicated than a serial crawler 43:26 it creates a thread for each fetch it 43:31 does alright but the huge difference is 43:33 that it does with two things one it does 43:38 the bookkeeping required to notice when 43:40 all of the crawls have finished and it 43:44 handles the shared table of which URLs 43:47 have been crawled correctly so this code 43:49 still has this table of URLs and that's 43:53 this F dot fetched this F dot fetch 43:59 map at line 43 but this this table is 44:06 actually shared by all of the all of the 44:10 crawler threads and all the collar 44:12 threads are making or executing inside 44:14 concurrent mutex and so we still have 44:16 this sort of tree up in current mutexes 44:18 that's exploring different parts of the 44:20 web graph but each one of them was 44:22 launched as a as his own go routine 44:25 instead of as a function call but 44:28 they're all sharing this table of state 44:30 this table of test URLs because if one 44:32 go routine fetches a URL we don't want 44:34 another girl routine to accidentally 44:36 fetch the same URL and as you can see 44:40 here line 42 and 45 I've surrounded them 44:43 by the new taxes that are required to to 44:48 prevent a race that would occur if I 44:51 didn't add them new Texas so the danger 44:52 here is that at line 43 a thread is 44:57 checking of URLs already been fetched so 44:59 two threads happen to be following the 45:02 same URL now two calls to concurrent 45:06 mutex end up looking at the same URL 45:09 maybe because that URL was mentioned in 45:11 two different web pages if we didn't 45:13 have the lock they'd both access the 45:17 math table to see if the threaded and 45:18 then already if the URL had been already 45:20 fetched and they both get false at line 45:23 43 they both set the URLs entering the 45:27 table to true at line 44 and at 47 they 45:30 will both see that I already was false 45:32 and then they both go on to patch the 45:33 web page so we need the lock there and 45:37 the way to think about it I think is 45:38 that we want lines 43 and 44 to be 45:41 atomic that is we don't want some other 45:44 thread to to get in and be using the 45:45 table between 43 and 44 we we want to 45:48 read the current content each thread 45:50 wants to read the current table contents 45:52 and update it without any other thread 45:55 interfering and so that's what the locks 45:57 are doing for us okay so so actually any 46:01 questions about the about the locking 46:03 strategy here 46:07 all right once we check the URLs entry 46:10 in the table alliant 51 it just crawls 46:13 it just fetches that page in the usual 46:15 way and then the other thing interesting 46:18 thing that's going on is the launching 46:20 of the threads yes so the question is 46:35 what's with the F dot no no the MU it is 46:43 okay so there's a structure to find out 46:47 line 36 that sort of collects together 46:50 all the different stuff that all the 46:53 different state that we need to run this 46:55 crawl and here it's only two objects but 46:57 you know it could be a lot more and 46:58 they're only grouped together for 47:00 convenience there's no other 47:02 significance to the fact there's no deep 47:05 significance the fact that mu and fetch 47:07 store it inside the same structure and 47:11 that F dot is just sort of the syntax 47:14 are getting out one of the elements in 47:15 the structure so I just happened to put 47:17 them you in the structure because it 47:19 allows me to group together all the 47:21 stuff related to a crawl but that 47:22 absolutely does not mean that go 47:25 associates the MU with that structure or 47:28 with the fetch map or anything it's just 47:30 a lock objects and just has a lock 47:33 function you can call and that's all 47:35 that's going on 47:53 so the question is how come in order to 47:58 pass something by reference I had to use 48:00 star here where it is when a in the 48:02 previous example when we were passing a 48:03 map we didn't have to use star that is 48:06 didn't have to pass a pointer I mean 48:07 that star notation you're seeing there 48:09 in mine 41 basically and he's saying 48:15 that we're passing a pointer to this 48:16 fetch state object and we want it to be 48:19 a pointer because we want there to be 48:20 one object in memory and all the 48:22 different go routines I want to use that 48:23 same object so they all need a pointer 48:25 to that same object so so we need to 48:28 find your own structure that's sort of 48:29 the syntax you use for passing a pointer 48:30 the reason why we didn't have to do it 48:32 with map is because although it's not 48:35 clear from the syntax a map is a pointer 48:39 it's just because it's built into the 48:42 language they don't make you put a star 48:45 there but what a map is is if you 48:50 declare a variable type map what that is 48:52 is a pointer to some data in the heap so 48:55 it was a pointer anyway and it's always 48:57 passed by reference do they you just 48:59 don't have to put the star and it does 49:00 it for you 49:01 so there's they're definitely map is 49:03 special you cannot define map in the 49:06 language it's it has to be built in 49:07 because there's some curious things 49:09 about it okay good okay so we fetch the 49:15 page now we want to fire off a crawl go 49:18 routine for each URL mentioned in the 49:20 page we just fetch so that's done in 49:23 line 56 on line 50 sisters loops over 49:26 the URLs that the fetch function 49:29 returned and for each one fires off a go 49:32 routine at line 58 and that lines that 49:35 func syntax in line 58 is a closure or a 49:41 sort of immediate function but that func 49:43 thing keyword is doing is to clearing a 49:46 function right there that we then call 49:49 so the way to read it maybe is 49:53 that if you can declare a function as a 49:56 piece of data as just func you know and 50:00 then you give the arguments and then you 50:03 give the body and that's a clears and so 50:08 this is an object now this is like it's 50:12 like when you type one when you have a 50:14 one or 23 or something you're declaring 50:18 a sort of constant object and this is 50:19 the way to define a constant function 50:21 and we do it here because we want to 50:24 launch a go routine that's gonna run 50:25 this function that we declared right 50:27 here and so we in order to make the go 50:29 routine we have to add a go in front to 50:31 say we want to go routine and then we 50:33 have to call the function because the go 50:35 syntax says the syntax of the go 50:37 keywords as you follow it by a function 50:39 name and arguments you want to pass that 50:40 function and so we're gonna pass some 50:43 arguments here and there's two reasons 50:50 we're doing this well really this one 50:52 reason we you know in some other 50:55 circumstance we could have just said go 50:57 concurrent mutex oh I concur mutex is 51:00 the name of the function we actually 51:01 want to call with this URL but we want 51:06 to do a few other things as well so we 51:08 define this little helper function that 51:10 first calls concurrent mutex for us with 51:12 the URL and then after them current 51:15 mutex is finished we do something 51:17 special in order to help us wait for all 51:19 the crawls to be done before the outer 51:22 function returns so that brings us to 51:24 the the weight group the weight group at 51:27 line 55 it's a just a data structure to 51:29 find by go to help with coordination and 51:33 the game with weight group is that 51:35 internally it has a counter and you call 51:39 weight group dot add like a line 57 to 51:43 increment the counter and we group done 51:46 to decrement it and then this weight 51:48 what this weight method called line 63 51:50 waits for the counter to get down to 51:53 zero so a weight group is a way to wait 51:56 for a specific number of things to 51:59 finish and it's useful in a bunch of 52:02 different situations here we're using it 52:04 to wait for the last go routine to 52:05 finish 52:05 because we add one to the weight group 52:07 for every go routine we create line 60 52:11 at the end of this function we've 52:13 declared decrement the counter in the 52:15 weight group and then line three weights 52:18 until all the decrements have finished 52:20 and so the reason why we declared this 52:22 little function was basically to be able 52:23 to both call concurrently text and call 52:26 dot that's really why we needed that 52:28 function so the question is what if one 52:39 of the subroutines fails and doesn't 52:43 reach the done line that's a darn good 52:45 question there is you know if I forget 52:49 the exact range of errors that will 52:51 cause the go routine to fail without 52:53 causing the program to feel maybe 52:55 divides by zero I don't know where 52:56 dereference is a nil pointer 52:57 not sure but there are certainly ways 52:59 for a function to fail and I have the go 53:04 routine die without having the program 53:06 die and that would be a problem for us 53:08 and so really the white right way to I'm 53:12 sure you had this in mind and asking the 53:13 question the right way to write this to 53:15 be sure that the done call is made no 53:18 matter why this guru team is finishing 53:20 would be to put a defer here which means 53:27 call done before the surrounding 53:31 function finishes and always call it no 53:34 matter why the surrounding function is 53:36 finished yes 53:53 and yes yeah so the question is how come 53:58 two users have done in different threads 54:00 aren't a race yeah so the answer must be 54:08 that internally dot a weight group has a 54:10 mutex or something like it that each of 54:14 Dunn's methods acquires before doing 54:18 anything else so that simultaneously 54:19 calls to a done to await groups methods 54:22 are trees we could to did a low class 54:39 yeah for certain leaf C++ and in C you 54:43 want to look at something called P 54:45 threads for C threads come in a library 54:47 they're not really part of the language 54:48 called P threads which they have these 54:51 are extremely traditional and ancient 54:55 primitives that all languages yeah 55:06 say it again you know not in this code 55:12 but you know you could imagine a use of 55:14 weight groups I mean weight groups just 55:15 count stuff and yeah yeah yeah weight 55:21 group doesn't really care what you're 55:22 pounding or why I mean you know this is 55:27 the most common way to see it use you're 55:45 wondering why you is passed as a 55:48 parameter to the function at 58 okay 55:54 yeah this is alright so the question is 55:59 okay so actually backing up a little bit 56:01 the rules for these for a function like 56:05 the one I'm defining on 58 is that if 56:09 the function body mentions a variable 56:10 that's declared in the outer function 56:14 but not shadowed then the the inner 56:17 functions use of that is the same 56:19 variable in the inner function as in the 56:20 outer function and so that's what's 56:23 happening with Fechter for example like 56:26 what is this variable here refer to what 56:28 does the Fechter variable refer to in 56:30 the inner function well it refers it's 56:32 the same variable as as the fetcher in 56:35 the outer function says just is that 56:37 variable and so when the inner function 56:38 refers to fetcher it just means it's 56:40 just referring the same variable as this 56:42 one here and the same with F f is it's 56:45 used here it's just is this variable so 56:48 you might think that we could get rid of 56:50 the this u argument that we're passing 56:55 and just have the inner function take no 56:57 arguments at all but just use the U that 56:59 was defined up on line 56 in the loop 57:05 and it'll be nice if we could do that 57:07 because save us some typing it turns out 57:09 not to work and the reason is that the 57:12 semantics of go of the for loop at line 57:16 56 is that the 57:17 for the updates the variable you so in 57:21 the first iteration of the for loop that 57:23 variable u contains some URL and when 57:29 you enter the second iteration before 57:31 the that variable this contents are 57:34 changed to be the second URL and that 57:37 means that the first go routine that we 57:39 launched that's just looking at the 57:41 outer if it we're looking at the outer 57:43 functions u variable the that first go 57:46 team we launched would see a different 57:48 value in the u variable after the outer 57:51 function it updated it and sometimes 57:53 that's actually what you want so for 57:55 example for for F and then particular F 57:58 dot fetched we interaction absolutely 58:01 wants to see changes to that map but for 58:04 you we don't want to see changes the 58:06 first go routine we spawn should read 58:09 the first URL not the second URL so we 58:12 want that go routine to have a copy you 58:13 have its own private copy of the URL and 58:16 you know is we could have done it in 58:18 other ways we could have but the way 58:20 this code happens to do it to produce 58:22 the copy private to that inner function 58:25 is by passing the URLs in argument yes 58:34 yeah if we have passed the address of 58:36 you yeah then it uh it's actually I 58:51 don't know how strings work but it is 58:52 absolutely giving you your own private 58:54 copy of the variable you get your own 59:00 copy of the variable and it yeah 59:26 are you saying we don't need to play 59:28 this trick in the code we definitely 59:33 need to play this trick in the code and 59:35 what's going on is this it's so the 59:37 question is Oh strings are immutable 59:39 strings are immutable right yeah so how 59:41 kind of strings are immutable how can 59:43 the outer function change the string 59:45 there should be no problem the problem 59:47 is not that the string is changed the 59:49 problem is that the variable U is 59:51 changed so the when the inner function 59:56 mentions a variable that's defined in 59:57 the outer function it's referring to 59:59 that variable and the variables current 60:01 value so when you if you have a string 60:03 variable that has has a in it and then 60:06 you assign B to that string variable 60:09 you're not over writing the string 60:10 you're changing the variable to point to 60:12 a different string and and because the 60:15 for loop changes the U variable to point 60:18 to a different string you know that 60:21 change to you would be visible inside 60:22 the inner function and therefore the 60:24 inner function needs its own copy of the 60:26 variable 60:36 essentially make a copy of that so that 60:50 okay but that is what we're doing in 60:53 this code and that's that is why this 60:54 code works okay 60:56 the proposal or the broken code that 60:59 we're not using here I will show you the 61:00 broken code 61:44 this is just like a horrible detail but 61:46 it is unfortunately one that you'll run 61:47 into while doing the labs so you should 61:50 be at least where that there's a problem 61:52 and when you run into it maybe you can 61:54 try to figure out the details okay 62:12 that's a great question so so the 62:15 question is you know if you have an 62:18 inner function just a repeated if you 62:19 have an inner function that refers to a 62:21 variable in the surrounding function but 62:23 the surrounding function returns what is 62:25 the inner functions variable referring 62:28 to anymore since the outer function is 62:30 as returned and the answer is that go 62:32 notices go analyzes your inner functions 62:35 or these are called closures go analyzes 62:38 them the compiler analyze them says aha 62:39 oh this disclosure this inner function 62:41 is using a variable in the outer 62:42 function we're actually gonna and the 62:44 compiler will allocate heat memory to 62:47 hold the variable the you know the 62:50 current value of the variable and both 62:52 functions will refer to that that little 62:55 area heap that has the barrel so it 62:58 won't be allocated the variable won't be 62:59 on the stack as you might expect it's 63:01 moved to the heap if if the compiler 63:03 sees that it's using a closure and then 63:04 when the outer function returns the 63:06 object is still there in the heap the 63:07 inner function can still get at it and 63:09 then the garbage collector is 63:11 responsible for noticing that the last 63:13 function to refer to this little piece 63:15 of heat that's exited returned and to 63:18 free it only then okay okay 63:24 okay so wait group wait group is maybe 63:29 the more important thing here that the 63:30 technique that this code uses to wait 63:32 for all the all this level of crawls to 63:35 finished all its direct chill and the 63:37 finish is the wait group of course 63:39 there's many of these wait groups one 63:41 per call two concurrent mutex each call 63:44 that concurrent mutex just waits for its 63:46 own children to finish and then returns 63:49 okay so back to the lock actually 63:53 there's one more thing I want to talk 63:54 about with a lock and that is to explore 63:56 what would happen if we hadn't locked 63:57 right I'm claiming oh you know you don't 64:00 lock you're gonna get these races you're 64:02 gonna get incorrect execution whatever 64:05 let's give it a shot I'm gonna I'm gonna 64:11 comment out the locks and the question 64:14 is what happens if I run the code with 64:17 no locks what am I gonna see so we may 64:24 see a ru or I'll call twice or I fetch 64:26 twice yeah that's yeah that would be the 64:28 error you might expect alright so I'll 64:31 run it without locks and we're looking 64:34 at the concurrent map the one in the 64:36 middle this time it doesn't seem to have 64:38 fetched anything twice it's only five 64:40 run again gosh so far genius so maybe 64:49 we're wasting our time with those locks 64:50 yeah never seems to go wrong I've 64:52 actually never seem to go wrong so the 64:57 code is nevertheless wrong and someday 65:00 it will fail okay the problem is that 65:03 you know this is only a couple of 65:04 instructions here and so the chances of 65:06 these two threads which are maybe 65:07 hundreds of instructions happening to 65:09 stumble on this you know the same couple 65:12 of instructions at the same time is 65:14 quite low and indeed and and this is a 65:17 real bummer about buggy code with races 65:20 is that it usually works just fine but 65:23 it probably won't work when the customer 65:25 runs it on their computer 65:28 so it's actually bad news for us right 65:30 what do we you know it it can be in 65:32 complex programs quite difficult to 65:34 figure out if you have a race right and 65:37 you might you may have code that just 65:39 looks completely reasonable that is in 65:41 fact sort of unknown to you using shared 65:44 variables and the answer is you really 65:47 the only way to find races in practice 65:50 to be is you automated tools and luckily 65:53 go actually gives us this pretty good 65:55 race detector built-in to go and you 66:00 should use it so if you pass the - race 66:04 flag when you have to get your go 66:06 program and run this race detector which 66:09 well I'll run the race detector and 66:11 we'll see so it emits an error message 66:16 from us it's found a race and it 66:19 actually tells us exactly where the race 66:21 happened so there's a lot of junk in 66:23 this output but the really critical 66:25 thing is that the race detector realize 66:28 that we had read a variable that's what 66:30 this read is that was previously written 66:32 and there was no intervening release and 66:35 acquire of a lock that's what that's 66:37 what this means furthermore it tells us 66:40 the line number so it's told us that the 66:43 read was a line 43 and the write the 66:49 previous write was at line 44 and indeed 66:51 we look at the code and the read isn't 66:53 line 43 and the right is at lying 44 so 66:56 that means that one thread did a write 66:58 at line 44 and then without any 67:00 intervening lock and another thread came 67:02 along and read that written data at line 67:05 43 that's basically what the race 67:07 detector is looking for the way it works 67:10 internally is it allocates sort of 67:11 shadow memory now lucky some you know it 67:15 uses a huge amount of memory and 67:16 basically for every one of your memory 67:17 locations the race detector is allocated 67:19 a little bit of memory itself in which 67:21 it keeps track of which threads recently 67:24 read or wrote every single memory 67:26 location and then when and it also to 67:28 keep tracking keeping track of when 67:30 threads acquiring release locks and do 67:32 other synchronization activities that it 67:35 knows forces but force threads to not 67:37 run 67:39 and if the race detector driver sees a 67:40 ha there was a memory location that was 67:42 written and then read with no 67:45 intervening market it'll raise an error 67:49 yes I believe it is not perfect yeah I 68:06 have to think about it what one 68:12 certainly one way it is not perfect is 68:15 that if you if you don't execute some 68:18 code the race detector doesn't know 68:21 anything about it so it's not analyzing 68:25 it's not doing static analysis the 68:27 racing sectors not looking at your 68:29 source and making decisions based on the 68:31 source it's sort of watching what 68:33 happened at on this particular run of 68:35 the program and so if this particular 68:37 run of the program didn't execute some 68:39 code that happens to read or write 68:42 shared data then the race detector will 68:44 never know and there could be erased 68:46 there so that's certainly something to 68:48 watch out for so you know if you're 68:49 serious about the race detector you need 68:50 to set up sort of testing apparatus that 68:53 tries to make sure all all the code is 68:55 executed but it's it's it's very good 68:59 and you just have to use it for your 8 69:01 to 4 lives okay so this is race here and 69:07 of course the race didn't actually occur 69:09 what the race editor did not see was the 69:12 actual interleaving simultaneous 69:14 execution of some sensitive code right 69:17 it didn't see two threads literally 69:18 execute lines 43 and 44 at the same time 69:21 and as we know from having run the 69:23 things by hand that apparently doesn't 69:25 happen only with low probability all it 69:28 saw was at one point that was a right 69:29 and they made me much later there was a 69:31 read with no intervening walk and so 69:37 enact in that sense it can sort of 69:39 detect races that didn't actually happen 69:41 or didn't really cause bugs okay 69:49 okay one final question about this this 69:52 crawler how many threads does it create 70:03 yeah and how many concurrent threads 70:10 could there be yeah so a defect in this 70:24 crawler is that there's no obvious bound 70:27 on the number of simultaneous threads 70:28 that might create you know with the test 70:30 case which only has five URLs big 70:32 whoopee but if you're crawling a real 70:34 wheel web with you know I don't know are 70:36 there billions of URLs out there maybe 70:38 not we certainly don't want to be in a 70:40 position where the crawler might 70:41 accidentally create billions of threads 70:43 because you know thousands of threads 70:46 it's just fine billions of threads it's 70:47 not okay because each one sits on some 70:51 amount of memory so a you know there's 70:54 probably many defects in real life for 70:56 this crawler but one at the level we're 70:58 talking about is that it does create too 71:00 many threads and really ought to have a 71:01 way of saying well you can create 20 71:03 threads or 100 threads or a thousand 71:04 threads but no more so one way to do 71:06 that would be to pre create a pool a 71:08 fixed size pool of workers and have the 71:11 workers just iteratively look for 71:13 another URL to crawl crawl that URL 71:14 rather than creating a new thread for 71:18 each URL okay so next up I want to talk 71:21 about a another crawler that's 71:23 implemented and a significantly 71:25 different way using channels instead of 71:28 shared memory it's a member on the mutex 71:31 call or I just said there is this table 71:33 of URLs that are called that's shared 71:34 between all the threads and asked me 71:36 locked this version does not have such a 71:40 table does not share memory and does not 71:44 need to use locks okay so this one the 71:52 instead there's basically a master 71:55 thread that's his master function on a 71:57 decent 986 and it has a table but the 72:00 table is private to the master function 72:02 and what the master function is doing is 72:06 instead of sort of basically creating a 72:09 tree of functions that corresponds to 72:11 the exploration of the graph which the 72:13 previous crawler did this one fires off 72:17 one ute one guru team per URL that it's 72:21 fetches and that but it's only the 72:23 master only the one master that's 72:26 creating these threads so we don't have 72:28 a tree of functions creating threads we 72:30 just have the one master okay so it 72:35 creates its own private map a line 88 72:37 this record what it's fetched and then 72:41 it also creates a channel just a single 72:44 channel that all of its worker threads 72:46 are going to talk to and the idea is 72:49 that it's gonna fire up a worker thread 72:50 and each worker thread that it fires up 72:53 when it finished such as fetching the 72:55 page will send exactly one item back to 72:58 the master on the channel and that item 73:00 will be a list of the URLs in the page 73:03 that that worker thread fetched so the 73:07 master sits in a loop we're in line 73:10 eighty nine is reading entries from the 73:13 channel and so we have to imagine that 73:16 it's started up some workers in advance 73:20 and now it's reading the information the 73:22 URL lists that those workers send back 73:24 and each time he gets a URL is sitting 73:26 on land eighty nine it then loops over 73:28 the URLs in that URL list from a single 73:32 page fetch align ninety and if the URL 73:36 hasn't already been fetched it fires off 73:39 a new worker at line 94 to fetch that 73:42 URL and if we look at the worker code 73:44 online starting line 77 basically calls 73:47 his fetcher and then sends a message on 73:51 the channel a line 80 or 82 saying 73:53 here's the URLs in the page they fetched 73:57 and notice that now that the maybe 74:01 interesting thing about this is that the 74:03 worker threads don't share any objects 74:07 there's no shared object between the 74:10 workers and the master so we don't have 74:11 to worry about locking we don't have to 74:12 worry about rhesus instead this is a 74:16 example of sort of communicating 74:18 information instead of getting at it 74:21 through shared memory yes 74:33 yeah yeah so the observation is that the 74:38 code appears but the workers are the 74:40 observation is the workers are modifying 74:42 ch while the Masters reading it and 74:49 that's not the way the go authors would 74:51 like you to think about this the way 74:54 they want you to think about this is 74:55 that CH is a channel and the channel has 74:58 send and receive operations and the 75:00 workers are sending on the channel while 75:03 the master receives on the channel and 75:05 that's perfectly legal the channel is 75:09 happy I mean what that really means is 75:11 that the internal implementation of 75:12 channel has a mutex in it and the 75:15 channel operations are careful to take 75:19 out the mutex when they're messing with 75:20 the channels internal data to ensure 75:22 that it doesn't actually have any 75:24 reasons in it but yeah channels are sort 75:27 of protected against concurrency and 75:29 you're allowed to use them concurrently 75:30 from different threads yes 75:36 over the channel receive yes 75:53 we don't need to close the channel I 75:56 mean okay the the break statement is 75:58 about when the crawl has completely 76:00 finished and we fetched every single URL 76:03 right because hey what's going on is the 76:06 master is keeping I mean this n value is 76:09 private value and a master every time it 76:13 fires off a worker at increments the end 76:14 though every worker it starts since 76:17 exactly one item on the channel and so 76:20 every time the master reads an item off 76:21 the channel it knows that one of his 76:23 workers is finished and when the number 76:24 of outstanding workers goes to zero then 76:29 we're done and we don't once the number 76:32 of outstanding workers goes to zero then 76:34 the only reference to the channel is 76:36 from the master or from oh really from 76:40 the code that calls the master and so 76:41 the garbage collector will very soon see 76:43 that the channel has no references to it 76:45 and will free the channel so in this 76:48 case sometimes you need to close 76:50 channels but actually I rarely have to 76:53 close channels 77:03 he said again 77:09 so the question is alright so you can 77:12 see at line 106 before calling master 77:16 concurrent channel sort of fires up one 77:19 shoves one URL into the channel and it's 77:25 to sort of get the whole thing started 77:26 because the code for master was written 77:28 you know the master goes right into 77:29 reading from the channel line 89 so 77:31 there better be something in the channel 77:33 otherwise line 89 would block forever so 77:36 if it weren't for that little code at 77:38 line 107 the for loop at 89 would block 77:42 reading from the channel forever and 77:44 this code wouldn't work well yeah so the 77:54 observation is gosh you know wouldn't it 77:56 be nice to be able to write code that 77:57 would be able to notice if there's 77:59 nothing waiting on the channel and you 78:01 can if you look up the Select statement 78:03 it's much more complicated than this but 78:05 there is the Select statement which 78:06 allows you to proceed to not block if 78:09 something if there's nothing waiting on 78:11 the channel 78:44 because the work resin finish okay sorry 78:59 to the first question is there I think 79:02 what you're really worried about is 79:03 whether we're actually able to launch 79:05 parallel so the very first step won't be 79:09 in parallel because there's an exit 79:37 owner the for-loop weights in at line 89 79:40 that's not okay that for loop at line 89 79:44 is does not just loop over the current 79:47 contents of the channel and then quit 79:49 that is the for loop at 89 is going to 79:54 read it may never exit but it's gonna 79:58 read it's just going to keep waiting 79:59 until something shows up in the channel 80:01 so if you don't hit the break at line 99 80:04 the for loop own exit yeah alright I'm 80:10 afraid we're out of time we'll continue 80:12 this actually we have a presentation 80:15 scheduled by the TAS which I'll talk 80:18 more about go