Java转Go—15Channel

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 string2)  // 初始化一个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 ,一个将元素发送到通道中,另一个从通道中取出元素。

运行结果:

Java转Go—15Channel
运行结果

无缓冲通道

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

代码示例:

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

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

运行结果:

Java转Go—15Channel
运行结果

有缓冲通道

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

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

代码示例:

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

运行结果:

Java转Go—15Channel
运行结果

close

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

代码示例:

func main() {
 ch := make(chan int2)
 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 int2)
 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 int2)
 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

运行结果:

Java转Go—15Channel
运行结果


原文始发于微信公众号(良猿):Java转Go—15Channel

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215800.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!