指定通道的执行顺序

尽管您不应该对 goroutines 的执行顺序做任何假设,但当您需要控制这个顺序时是可以的。这小节介绍一个使用 信号通道 的技巧。

您可能会问为什么简单函数能够更容易的控制执行顺序,还要选择按顺序执行 goroutines。这个答案很简单:goroutines 能够操作并发并等待其他 goroutines 结束,而在普通的函数却不能!

说明这个主题的 Go 程序命名为 defineOrder.go,并分了五部分来介绍。

defineOrder.go 的第一部分如下:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func A(a, b chan struct{}) {
  7. <-a
  8. fmt.Println("A()!")
  9. time.Sleep(time.Second)
  10. close(b)
  11. }

A() 函数能被存储在 a 参数的通道中并阻塞。一旦在 main 函数中通道被解除阻塞,函数 A() 将开始工作。最后,它关闭 b 通道,解除在函数 B() 中的另个函数的阻塞。

defineOrder.go 的第二段代码如下:

  1. func B(a, b chan struct{}) {
  2. <-a
  3. fmt.Println("B()!")
  4. close(b)
  5. }

B() 中的逻辑和函数 A() 相同。这个函数被阻塞直到 a 通道关闭。然后它做自己的工作并关闭 b 通道。注意 ab 通道值这个函数的参数名。

defineOrder.go 的第三段代码如下:

  1. func C(a chan struct{}) {
  2. <-a
  3. fmt.Println("C()!")
  4. }

C() 函数被阻塞并等待 a 通道关闭再开始工作。

defineOrder.go 的第四部分如下:

  1. func main() {
  2. x := make(chan struct{})
  3. y := make(chan struct{})
  4. z := make(chan struct{})

这三个通道作为三个函数的参数。

defineorder.go 的最后一段如下:

  1. go C(z)
  2. go A(x, y)
  3. go C(z)
  4. go B(y, z)
  5. go C(z)
  6. close(x)
  7. time.Sleep(3 * time.Second)
  8. }

执行 defineOrder.go 将产生期望的输出,即使 C() 函数被调用了多次:

  1. $go run defineOrder.go
  2. A()!
  3. B()!
  4. C()!
  5. C()!
  6. C()!
  7. C()!

以 goroutines 调用 C() 函数多次会工作的很好,因为 C() 没有关闭任何通道。然而,如果您调用 A()B() 多次的话,您可能会得到如下错误信息:

""

如您所见,A() 函数被调用了俩次。然而,由于 A() 函数关闭了一个通道,它的 goroutines 中的一个将发现通道已经关闭并产生一个崩溃。如果您调用 B 多次会得到同样的崩溃信息。