写点什么

Go 语言入门 14—Channel

作者:良猿
  • 2022-11-06
    湖北
  • 本文字数:2604 字

    阅读完需:约 9 分钟

Go语言入门14—Channel

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
复制代码

通道操作

在 go 语言中,通道一共有 3 种操作,分别是:发送、接收和关闭,其中发送和接收都需要使用到一个符号:<- ,关闭使用 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

循环接收

在上面的代码中,receive 函数通过接收值的时候接收 ok 返回值判断通道是否关闭,除了使用这种方法之外,还可以使用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 函数的参数为只能发送类型,receive 函数的参数为只能接收类型,所以在 send 函数体内部就不能从通道接收,在 receive 函数体内部就不能发送值到通道中。例如在 receive 内往通道发送一个值就会报错:send to the receive-only type <-chan int


运行结果:



发布于: 刚刚阅读数: 3
用户头像

良猿

关注

还未添加个人签名 2019-02-13 加入

还未添加个人简介

评论

发布
暂无评论
Go语言入门14—Channel_Go_良猿_InfoQ写作社区