I'm not a native English speaker, and I'm unsure about the meaning of term invariants. I just take it as something that won't change.
See marco.m's comment that links to What is an invariant? for more about invariants.
The reason we may need a mutex in Go—or indeed, in any language with concurrency—is that programs that are trying to maintain some invariant may deliberately allow the invariant to be violated briefly. In a programming language that has no concurrency (and therefore isn't Go), we might have code like this:
// invariants: bestThing is the best thing (according to goodness ranking) in
// allThings; allThings[0] is always the initial badThing.
var bestThing Thing = badThing
var allThings []Thing = { badThing }
func addThing(t Thing) {
allThings = append(allThings, t)
// now, if t is better than the best thing so far, set bestThing = t
if goodness(t) > goodness(bestThing) {
bestThing = t
}
}
This is terrible code for numerous reasons (global variables for instance), but, as long as our language has no concurrency, we note that addThing
maintains the invariant by adding something to allThings
and updating bestThing
if needed.
If we add concurrency to our language, though, addThing
itself can break. Suppose that in one thread / goroutine / whatever, we call addThing
with a one pretty-good thing, and in some other thread, we call addThing
with another, different, pretty-good thing. One of these threads or goroutines or whatever we call these parallel operators begins modifying both the allThings
table and the bestThing
variable. The other one does the same. We might:
- lose one of the things, by storing in
allThings
the wrong value; and/or
- rate the second-best thing as the best thing
because the invariant itself is destroyed at the beginning (with allThings = append(allThings, t)
, which writes on one global variable, and only restored by the time the function returns (having checked the goodness and updated the best-thing global variable). We can—clumsily—repair this problem with a mutex:
func addThing(t Thing) {
someLock.Lock()
defer someLock.Unlock()
allThings = append(allThings, t)
// now, if t is better than the best thing so far, set bestThing = t
if goodness(t) > goodness(bestThing) {
bestThing = t
}
}
The mutex ensures that, if two different goroutines (or whatever they are) enter addThing
, one of them stops and waits for the other to return before proceeding. The one that continues can break and then restore the invariant, and then the other can break and then restore the invariant.
(This clumsy repair is still not very good: for one thing, we now need to wrap each use of bestThing
with a similar mutex, so that we don't read it while the routine is writing it. But the mutex gives us a tool with which we can deal with the issue. Using global variables like this, in goroutines in real Go programs, is "communicating by sharing", which Go discourages in favor of "sharing by communicating" over channels. Of course the data structures would need to be reworked to do that, getting rid of these simple global variables for instance. That's a good thing on its own, too!)