Channel

channel(通道)在 go 语言中通常用于goroutine之间通信,可以连接不同的 goroutine , channel 是一种可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

channel 是一种特殊的类型,类似于数据结构中的队列,其中的元素遵循先入先出的规则,同时每一个通道都需要指定对应的类型,指定类型之后该通道就只能发送或接受对应类型的数据。

通道声明

// 语法:
var 变量名 chan 元素类型
// 示例:
var ch1 chan int     // 声明一个int类型的通道
var ch2 chan string   // 声明一个string类型的通道

通道初始化

通道是引用类型,其默认值为 nil ,如果一个通道只声明没有初始化那么直接使用这个通道会触发 panic 。通道初始化可以使用 make 函数进行。

语法:

make(chan 元素类型, [缓冲大小])

使用make函数初始化通道时,需要指定通道的元素类型,通道的缓冲大小可以省略。

代码示例:

ch1 := make(chan int)   // 初始化一个int类型的通道
ch2 := make(chan string, 2)  // 初始化一个string类型的通道,且缓冲区大小为2

通道操作

<-close

初始化一个通道:

ch := make(chan int)

发送:将一个 int 类型发送到通道里面

ch <- 10   // 把 10 发送到通道 ch 中   

接收:从通道里面接收一个 int 类型的元素

x := <- ch   // 从通道 ch 中接收元素并赋值给变量 x

关闭:关闭一个通道

close(ch)  // 调用内置的 close 函数关闭通道 ch

代码示例:

package main

import (
 "fmt"
 "time"
)

func main() {
 ch := make(chan int)
 go send(ch)
 go receive(ch)
 time.Sleep(time.Second)
}

func send(ch chan int) {
 ch <- 10
 fmt.Println("已发送数字 10 到通道中")
}

func receive(ch chan int) {
 x := <- ch
 fmt.Println("从通道接收到元素:", x)
}

上述代码中启动了两个 goroutine ,一个将元素发送到通道中,另一个从通道中取出元素。

运行结果:

运行结果

无缓冲通道

在使用 make 函数初始化通道时,如果没有指定缓冲区的大小,那么就默认是无缓冲通道,例如上面的代码示例中使用的就是无缓冲通道,**无缓冲通道在发送值的时候必须要有一个对应的 goroutine 接收值才行,**否则就会报错。

代码示例:

func main() {
 ch := make(chan int)
 ch <- 10  // 发送一个值到通道中
    fmt.Println("成功发送 10 到通道中")
}

上述代码中,使用的就是无缓冲通道,所以在第 3 行发送一个值到通道中时就会报错,导致程序运行失败。

运行结果:

运行结果

有缓冲通道

有缓冲通道表示通道里面可以缓冲一部分数据,发送的数据可以暂时的保存在通道的缓冲区,不需要有 goroutine 立即去接收。要使用有缓冲通道只需要在使用 make 函数初始化通道时指定缓冲区的大小即可,只要指定的缓冲区的大小大于 0 就行,缓冲区大小表示通道内能够暂存的元素个数。

针对上面无缓冲的代码,只需要在初始化时指定缓冲区大小,使其变成有缓冲通道,就不会报错了。

代码示例:

func main() {
 ch := make(chan int, 1)  // 初始化一个缓冲区为 1 的有缓冲通道
 ch <- 10     // 发送一个值到通道中
    fmt.Println("成功发送 10 到通道中")
}

运行结果:

运行结果

close

可以使用close关闭通道,当一个通道不再往里面发送值或者接收值的时候,就需要将通道关闭,通道是可以被垃圾回收机制回收的,它和关闭文件不一样,在结束操作之后关闭文件是必须的,但关闭通道不是必须的。

代码示例:

func main() {
 ch := make(chan int, 2)
 go send(ch)
 go receive(ch)
 time.Sleep(time.Second)
}

func send(ch chan int) {
 for i := 0; i < 10; i++ {
  ch <- i
 }
 close(ch)
}

func receive(ch chan int) {
 for  {
  x, ok := <- ch
  if ok {
   fmt.Println("接收到的值:", x)
  } else {
   break
  }
 }
}

代码中 send 方法循环发送从 0 到 9 到通道中,当发送完成之后就关闭通道,然后 receive 循环循环接收通道中的值,当通道被关闭之后接收到的 ok 值为 false ,则跳出循环。

关闭通道注意事项:

  1. 往一个已关闭的通道发送值会发生 panic。
  2. 从一个已关闭的通道接收值会一直接收到通道为空,通道为空之后再接收返回通道元素类型的默认值。
  3. 对一个已关闭的通道再次关闭会发生 panic

循环接收

for range

代码示例:

package main

import (
 "fmt"
 "time"
)

func main() {
 ch := make(chan int, 2)
 go send(ch)
 go receive(ch)
 time.Sleep(time.Second)
}

func send(ch chan int) {
 for i := 0; i < 10; i++ {
  ch <- i
 }
 close(ch)
}

func receive(ch chan int) {
 for i := range ch {
  fmt.Println("接收到的值:", i)
 }
}

上述代码 receive 函数中通过 for range 的方式从通道中获取值,当发送方关闭了通道之后,for range 循环也就自动退出了。

单向通道

之前介绍的一直都是一个通道既可以发送值也可以接收值,在并发编程中,通道经常被作为参数传递给不同的函数,有时候需要在不同的函数中对通道进行限制,这就需要用到单向通道了。

chan <- 类型<- chan 类型

对上面代码进行修改:

package main

import (
 "fmt"
 "time"
)

func main() {
 ch := make(chan int, 2)
 go send(ch)
 go receive(ch)
 time.Sleep(time.Second)
}

func send(ch chan <- int) {
 for i := 0; i < 10; i++ {
  ch <- i
 }
 close(ch)
}

func receive(ch <- chan int) {
 for i := range ch {
  fmt.Println("接收到的值:", i)
 }
}
send to the receive-only type <-chan int

运行结果:

运行结果