🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 9.4\. 错误的同步方式 注意:变量读操作虽然可以侦测到变量的写操作,但是并不能保证对变量的读操作就一定发生在写操作之后。 例如: ``` var a, b int func f() { a = 1; b = 2; } func g() { print(b); print(a); } func main() { go f(); g(); } ``` 函数g可能输出2,也可能输出0。 这种情形使得我们必须回避一些看似合理的用法。 这里用重复检测的方法来代替同步。在例子中,twoprint函数可能得到错误的值: ``` var a string var done bool func setup() { a = "hello, world"; done = true; } func doprint() { if !done { once.Do(setup); } print(a); } func twoprint() { go doprint(); go doprint(); } ``` 在doprint函数中,写done暗示已经给a赋值了。 但是没有办法给出保证,函数可能输出空的值(在2个goroutines中同时执行到测试语句)。 另一个错误陷阱是忙等待: ``` var a string var done bool func setup() { a = "hello, world"; done = true; } func main() { go setup(); for !done { } print(a); } ``` 我们没有办法保证在main中看到了done值被修改的同时也 能看到a被修改,因此程序可能输出空字符串。 更坏的结果是,main 函数可能永远不知道done被修改,因为在两个线程之间没有同步操作,这样main 函数永远不能返回。 下面的用法本质上也是同样的问题. ``` type T struct { msg string; } var g *T func setup() { t := new(T); t.msg = "hello, world"; g = t; } func main() { go setup(); for g == nil { } print(g.msg); } ``` 即使main观察到了 g != nil 条件并且退出了循环,但是任何然 不能保证它看到了g.msg的初始化之后的结果。 在这些例子中,只有一种解决方法:用显示的同步。