我不是以英语为母语的人,我不确定术语不变量的含义。我只是把它当作不会改变的东西。
有关不变量的更多信息,请参阅链接到 What is an invariant? 的 marco.m's comment。
我们可能在 Go 中需要互斥体的原因——或者实际上,在任何具有并发性的语言中——是因为试图维护某些不变量的程序可能故意允许短暂地违反不变量。在没有并发的编程语言中(因此不是 Go),我们可能有这样的代码:
// 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
}
}
这是一个糟糕的代码,原因有很多(例如全局变量),但是,只要我们的语言没有并发性,我们注意到 addThing 通过向 allThings 添加一些内容并更新 bestThing 来维护不变量需要。
如果我们将并发添加到我们的语言中,addThing 本身可能会中断。假设在一个线程/goroutine/whatever 中,我们用一个非常好的东西调用addThing,而在另一个线程中,我们用另一个不同的、非常好的东西调用addThing。这些线程或 goroutine 或我们称之为并行运算符的任何一个都开始修改 allThings 表和 bestThing 变量。另一个做同样的事情。我们可能:
- 通过在
allThings 中存储错误的值来丢失其中的一件事;和/或
- 把第二好的东西评为最好的东西
因为不变量本身在一开始就被破坏了(allThings = append(allThings, t),它写在一个全局变量上,只有在函数返回时才恢复(检查了好坏并更新了最好的全局变量)。我们可以——笨拙地——用互斥锁修复这个问题:
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
}
}
互斥锁确保,如果两个不同的 goroutine(或任何它们)进入addThing,其中一个会停止并等待另一个返回,然后再继续。继续的一个可以破坏然后恢复不变量,然后另一个可以破坏然后恢复不变量。
(这个笨拙的修复仍然不是很好:一方面,我们现在需要用类似的互斥锁来包装bestThing 的每次使用,这样我们就不会在例程编写它时读取它。但是mutex 为我们提供了一个工具来解决这个问题。在真正的 Go 程序的 goroutine 中使用像这样的全局变量是“通过共享进行通信”,而 Go 不鼓励通过通道“通过通信进行共享”。当然数据结构需要重新设计才能做到这一点,例如摆脱这些简单的全局变量。这本身也是一件好事!)