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 ,则跳出循环。
关闭通道注意事项:
-
往一个已关闭的通道发送值会发生 panic。 -
从一个已关闭的通道接收值会一直接收到通道为空,通道为空之后再接收返回通道元素类型的默认值。 -
对一个已关闭的通道再次关闭会发生 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
。
运行结果:

原文始发于微信公众号(良猿):Java转Go—15Channel
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215800.html