通道

Go 语言推荐使用消息传递实现并发通信,这种消息通信机制被称为 channel,中文译作「通道」,可理解为传递消息的通道。
通道是 Go 语言在语言级别提供的协程通信方式,它是一种数据类型,本身是并发安全的,我们可以使用它在多个 goroutine 之间传递消息,而不必担心通道中的值被污染。
通道是一种数据类型,和数组/切片类型类似,一个通道只能传递一种类型的值,这个类型需要在声明 通道时指定。在使用通道时,需要通过 make 进行声明,通道对应的类型关键字是 chan:

  1. ch := make(chan int)

我们可以把通道看作是一个先进先出(FIFO)的队列,通道中的元素会严格按照发送顺序排列,继而按照排列顺序被接收,通道元素的发送和接收都可以通过 <- 操作符来实现,发送时元素值在右,通道变量在左:

  1. ch <- 1 // 表示把元素 1 发送到通道 ch

接收时通道变量在右,可以通过指定变量接收元素值:

  1. element := <-ch

也可以留空表示忽略:

  1. <-ch
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func channelAdd(a, b int, ch chan int) {
  7. c := a + b
  8. fmt.Printf("%d+%d = %d \n", a, b, c)
  9. ch <- c
  10. }
  11. func main() {
  12. start := time.Now()
  13. chs := make([]chan int, 10)
  14. for i := 0; i < 10; i++ {
  15. chs[i] = make(chan int)
  16. go channelAdd(1, i, chs[i])
  17. }
  18. for _, ch := range chs {
  19. <- ch
  20. }
  21. end := time.Now()
  22. consume := string(end.Sub(start).String())
  23. fmt.Printf("coumse %s s", consume)
  24. }

我们首先定义了一个包含 10 个通道类型的切片 chs,并把切片中的每个通道分配给 10 个不同的协程。在每个协程的 add函数业务逻辑完成后,我们通过 ch<- 1 语句向对应的通道中发送一个数据。在所有的协程启动完成后,我们再通过 <-ch 语句从通道切片 chs 中依次接收数据(不对结果做任何处理,相当于写入通道的数据只是个标识而已,表示这个通道所属的协程逻辑执行完毕),直到所有通道数据接收完毕,然后打印主程序耗时并退出。
之所以上述这段代码可以实现和「共享内存+锁」一样的效果,是因为往通道写入数据和从通道接收数据都是原子操作,或者说是同步阻塞的,当我们向某个通道写入数据时,就相当于该通道被加锁,直到写入操作完成才能执行从该通道读取数据的操作,反过来,当我们从某个通道读取数据时,其他协程也不能操作该通道,直到读取完成,如果通道中没有数据,则会阻塞在这里,直到通道被写入数据。因此,可以看到通道的发送和接收操作是互斥的,同一时间同一个进程内的所有协程对某个通道只能执行发送或接收操作,两者不可能同时进行,这样就保证了并发的安全性,数据不可能被污染