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的初始化之后的结果。
在这些例子中,只有一种解决方法:用显示的同步。