CheckWebsites, that checks the status of a list of URLs.
truefor a good response,
falsefor a bad response.
WebsiteCheckerwhich takes a single URL and returns a boolean. This is used by the function to check all the websites.
CheckWebsitesso that we can see the effect of our changes.
CheckWebsitesusing a slice of one hundred urls and uses a new fake implementation of
slowStubWebsiteCheckeris deliberately slow. It uses
time.Sleepto wait exactly twenty milliseconds and then it returns true. We use
b.ResetTimer()in this test to reset the time of our test before it actually runs
go test -bench=.(or if you're in Windows Powershell
go test -bench="."):
CheckWebsiteshas been benchmarked at 2249228637 nanoseconds - about two and a quarter seconds.
CheckWebsitesfaster. Instead of waiting for a website to respond before sending a request to the next website, we will tell our computer to make the next request while it is waiting.
doSomething()we wait for it to return (even if it has no value to return, we still wait for it to finish). We say that this operation is blocking - it makes us wait for it to finish. An operation that does not block in Go will run in a separate process called a goroutine. Think of a process as reading down the page of Go code from top to bottom, going 'inside' each function when it gets called to read what it does. When a separate process starts it's like another reader begins reading inside the function, leaving the original reader to carry on going down the page.
gostatement by putting the keyword
goin front of it:
goin front of a function call, we often use anonymous functions when we want to start a goroutine. An anonymous function literal looks just the same as a normal function declaration, but without a name (unsurprisingly). You can see one above in the body of the
()at the end of the anonymous function is doing. Secondly they maintain access to the lexical scope they are defined in - all the variables that are available at the point when you declare the anonymous function are also available in the body of the function.
WebsiteCheckerfunction) each of which will add its result to the results map.
CheckWebsites, it's now returning an empty map. What went wrong?
forloop started had enough time to add their result to the
WebsiteCheckerfunction is too fast for them, and it returns the still empty map.
urlis reused for each iteration of the
forloop - it takes a new value from
urlseach time. But each of our goroutines have a reference to the
urlvariable - they don't have their own independent copy. So they're all writing the value that
urlhas at the end of the iteration - the last url. Which is why the one result we have is the last url.
u- and then calling the anonymous function with the
urlas the argument, we make sure that the value of
uis fixed as the value of
urlfor the iteration of the loop that we're launching the goroutine in.
uis a copy of the value of
url, and so can't be changed.
fatal error: concurrent map writes. Sometimes, when we run our tests, two of the goroutines write to the results map at exactly the same time. Maps in Go don't like it when more than one thing tries to write to them at once, and so
WARNING: DATA RACEis pretty unambiguous. Reading into the body of the error we can see two different goroutines performing writes on a map:
Write at 0x00c420084d20 by goroutine 8:
Previous write at 0x00c420084d20 by goroutine 7:
WebsiteCheckerfunction with the url.
resultsmap we now have a
resultChannel, which we
makein the same way.
chan resultis the type of the channel - a channel of
result. The new type,
resulthas been made to associate the return value of the
WebsiteCheckerwith the url being checked - it's a struct of
bool. As we don't need either value to be named, each of them is anonymous within the struct; this can be useful in when it's hard to know what to name a value.
mapdirectly we're sending a
resultstruct for each call to
resultChannelwith a send statement. This uses the
<-operator, taking a channel on the left and a value on the right:
forloop iterates once for each of the urls. Inside we're using a receive expression, which assigns a value received from a channel to a variable. This also uses the
<-operator, but with the two operands now reversed: the channel is now on the right and the variable that we're assigning to is on the left:
resultreceived to update the map.
wc, and each send to the result channel, is happening in parallel inside its own process, each of the results is being dealt with one at a time as we take values out of the result channel with the receive expression.
CheckWebsitesfunction; the inputs and outputs never changed, it just got faster. But the tests we had in place, as well as the benchmark we wrote, allowed us to refactor
CheckWebsitesin a way that maintained confidence that the software was still working, while demonstrating that it had actually become faster.