一光年

[Go-goroutine介绍] 2. 无缓冲通道

2019.06.27

Go语言的并发模型叫做通信顺序进程(Communicating Sequential Processes),简称CSP。不用于对数据进行加锁来实现同步访问,Go语言中通过CSP来实现goroutine之间的数据消息传递。

goroutine的语法非常简洁,只需要函数调用前使用关键字go即可,这样可以很方便的在新的routine中去执行相关处理。

  go printAlphabet('A')
  ...

  func printAlphabet(a int) {
    ...
    fmt.Printf("%c ", a)
  }

如果多个routine之间需要通信,那么就需要使用通道技术channel,通道允许多个goroutine共享并相互传递数据。

以下通过一个交替打印A-Z的示例程序来进行说明。

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {

    letter := make(chan int)     // 建立一个通道

    wg.Add(2)

    go printAlphabet(1, letter)  // 打印函数1,执行goroutine
    go printAlphabet(2, letter)  // 打印函数2,执行goroutine
	
    letter <- 'A'                // 往通道传入字母A,开始打印

    wg.Wait()                    // 等待所有goroutine完成
    fmt.Println("\nTerminating Program")
}

func printAlphabet(id int, letter chan int) {
    
    defer wg.Done()          // 方法结束时调用
    
    for {
        a, ok := <-letter    // 从通道内取的数据
        if !ok {             // 如果通道已关闭,方法执行结束
            return
        }
		
        fmt.Printf("%d-%c ", id, a)   // 打印字母

        if a == 'Z' {        // 如果打印的是字母Z,结束方法执行
            close(letter)    // 关闭通道
            return
        }

        a++;
        letter <- a          // 当前字母打印完成,往通道传下一个字母
    }
}

说明

  1. sync.WaitGroup是一个计数信号量,初始化为2[wg.Add(2)]。当goroutine方法中的wg.Done执行时,信号量会自减1。wg.Wait()会一直阻塞当前goroutine,直到信号量减为0才继续往下执行。
  2. 管道的读写使用<-操作符。当一个goroutine执行读操作,而通道的另一端还没有写入时,当前goroutine的处理会被阻塞,直到有数据被写入。
  3. 如果所有goroutine都在等待通道被写入,而没有处理去进行写操作,就会发生死锁的错误,因为当前所有goroutine都因为等待通道写入数据而被阻塞。

例如,把示例代码中主程序中的通道写入处理注释掉:

    ...
    // letter <- 'A'
    ...

执行main.go,将会发生以下错误:

fatal error: all goroutines are asleep - deadlock!

实例程序的时序图如下:

goroutine