一、Channel是什么
Channel是goroutine之间通信的管道,用于让一个goroutine发送特定值到另一个goroutine。
在Go语言中Channel是一种特殊的类型,总是遵循先入先出的策略来保证传输数据的顺序。并且Channel 在多并发操作里是属于协程安全的。
二、Channel的使用
2.1、Channel类型
Channel是一种引用类型,空值是nil。每一个Channel都是一个具体类型的管道,需要在生命是指定元素类型。声明一个管道的语句如下:
var 变量名 chan 元素类型
例如:
var ch1 chan int //声明一个传递整型的管道
var ch2 chan string //声明一个传递字符串的管道
2.2、Channel的创建
通道声明后需要使用make()函数进行初始化。创建Channel的格式如下:
make(chan 元素类型,[缓冲大小])
其中缓冲大小是可选参数,若传入该参数,则代表创建一个有缓冲的管道,不传则代表创建无缓冲的管道。例如:
ch2 := make(chan bool) //创建一个无缓冲的布尔型管道
ch3 := make(chan []int , 3) //创建一个有缓冲的int切片型管道
2.3、Channel的基本操作
Channel有发送、接收和关闭三种操作。发送和接收都使用“<-”符号操作:
ch := make(chan int) //创建一个int管道
ch <- 10 //将数字10放入管道
a := <- ch //从管道中接收值并赋值给变量a
<- ch //从管道中接收值并忽略
当Channel不再使用之后可以使用close() 函数来关闭:
close(ch)
要注意的是:
1.向关闭的通道再发送值就会导致panic。
2.对关闭的通道可以继续取值直到通道为空。
3.对已经关闭的空通道取值会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。
2.4、无缓冲的Channel
向一个无缓存Channel发送数据将导致发送者goroutine阻塞,直到另一个goroutine在相同的channel上执行接收操作,在该条数据传输成功之后,两个goroutine可以继续执行后面的语句。同样的,如果接收数据的操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。例如:
示例1:
func main() {
ch := make(chan int)
ch <- 10 //发送goroutine阻塞,直至其他goroutine将数据接收
fmt.Println("发送成功")
}
示例2:
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine接收值,该goroutine阻塞
ch <- 10 // 向管道中发送值,接收goroutine阻塞解除
fmt.Println("发送成功")
}
使用无缓冲管道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也称为同步通道。
2.5、有缓冲的Channel
有缓存的channel内部有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。例如:
ch = make(chan string, 3) // 创建一个可以缓存3个字符串的管道
向有缓存channel执行发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到另一个goroutine执行接收操作而释放了新的队列空间。同样,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。
可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量。
三、Channel的其他操作
3.1、判断Channel是否关闭
if v, ok := <-ch; !ok {
fmt.Println("channel 已关闭,读取不到数据")
}
3.2、循环获取
for data := range ch {
doSomething()
}
3.3、select
当与多个 goroutine 通信时,可以使用 select 来管理多个 channel 的通信数据:
for {
select {
case <-ch1:
doSomething1()
case <-ch2:
doSomething2()
}
}
作者:燕雨洁 部门:混合产品线