- So we've gone through the cache coherency,
file sharing issues, I've shown you how to do
synchronization with both
atomic instructions and mutexes.
I wanna show you something that's incredibly cool,
which is Go's race detector.
This has been in the language,
I think it's been around since 1.3.
What's really brilliant about the race detector
is that when it was introduced, they ran it
against the standard library
and they found races in the standard library.
I love when the Go language team creates tooling
and they find their own problems.
It was really brilliant stuff.
The race detector's built into the Go front end,
so when we do go build, it's built into go build.
It's also built into the go test.
I tend to use the race detector more on
go test than go build.
But I wanna show you how cool this is
because finding data races can be impossible.
I've been in shops before where it's taken like
three years to find data races,
and now with this tooling,
we can find it very quickly.
Let's go back to the original program we had
where we didn't make these three lines of code atomic.
Remember we've got the two goroutines.
Each goroutine's incrementing
that global variable twice; there it is.
Remember what happened when I put
the (mumbles) print statement in here,
then the race showed itself
just because of the output.
But if I come back here and build this code again,
go build, then we run it ...
Remember we're still getting the right answer of four.
There it is; four, four, four, four.
We don't get any visual clue that there's
a problem with this code, that things are not
atomic until I cause some sort of
synchronization issue right here.
So we do build again, I run it.
Now we're getting that, too.
Remember that from the first example.
But this is the cool part.
If I just do the following, go build -race,
then what's gonna happen is this code's gonna be
built with the race detector.
This code's gonna run probably 25 to 30% slower,
but now what happens when I run it?
The program stops.
We get a data race stack trace
and now we know exactly what's going on.
You can see the stack trace said that
you launched a goroutine on line 29;
goroutine 6 on line 29.
And then goroutine 7 had a read
while there was also a write going on
by the other goroutines, line 33 and 39.
In other words, we had a un-synchronized
read and write between line 33 and 39.
If we look at 33 and 39, there's our read,
then there's our write.
So yeah, there it is; the race detector identify
that we're going to have data corruption,
and it found it for us.
If you get a race detection like this
and it's not obvious why that race exists,
then I need you to cause pausing.
Because once the race detector finds something,
it should be obvious to you, if you have a good,
strong, mental model of the code
and it's not too clever, how two goroutines could've
came to that point at the same time.
But I've written code before that's gotten
so out of hand and complex on concurrency
that the race detector fires and I'm like,
"Oh my God, how did I get there?"
Usually that means that we're not gonna try to
fix it with a Band-Aid or just throw a mutex at it.
We've gotta probably refactor
some of that code to simplify things.
Even when they ran this thing as a standard library,
they found some interesting code that wasn't
obvious to anybody on how that data race was there.
This is really brilliant stuff.
Most of the time, I run the race detector on go test.
I'll say go test -race for those tests that I have
that are gonna be running in parallel ...
Where we're gonna have multiple goroutines.
That's where I tend to run it.
I have heard of companies that run on a Web farm
where you have multiple instances of your service.
They might run one instance of a binary
with the race detector on.
The general rule is this; if the race detector
finds a race, guess what?
You have a race; fix it.
You've got at least two goroutines
where one's doing a read and one's doing a write
to the same memory location at the same time.
But if the race detector doesn't find a race,
it doesn't mean you don't have one,
it just hasn't found it yet. (laughs)
I've been in environments where, on my Mac,
the race detector doesn't find anything
because the Scheduler isn't creating enough,
let's say, wacky concurrency or context switches.
And then the code moves to CI on some Linux platform
and CI keeps failing on the race detection.
You really should be running your tests
with the race detector on,
especially if you're writing multi-threaded software.
This race detector's very, very powerful.
You've got to make sure you're using it
when you're writing multi-threaded software in Go.