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的初始化之后的结果。

在这些例子中,只有一种解决方法:用显示的同步。