Channel是Go中的一个核心类型,可以把它看成一个管道,Goroutine通过它可以发送或者接收数据并进行通讯。它的操作符号是 <- ,就像map和slice一样,channel必须先创建才能够使用:
ch=make(chan int) // int表示chan里面的数据类型
ch :=make(chan interface{}) //任意数据类型的chan
Channel类型
Channel类型的定义格式如下:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
包括三种类型的定义。 <- 表示数据的流向,如果没有指定 <-,则表示chan既可以发送数据也可以接受数据。
Channel声明
chan int //既可以发送数据也可以接受数据
<- chan int //只允许从chan接受int类型数据
chan <- int //只允许发送int类型数据到chan
Channel设置容量
make(chan int ,100)
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
参数100用来设置channel的缓存容量,也是channel最大可以容纳的元素数量。
元素数量超过缓存大小,向channel发送数据会阻塞。
元素数量为空,从channel接受数据会阻塞。
如果缓存大小设置为0,发送和接受都将被阻塞。
Channel类型安全
多个goroutine向channel发送或者接受数据,是线程安全的,不要采取额外的同步措施。
Channel数据顺序
chnnel是一个FIFO(先进先出)队列,向channel发送数据的顺序和从channel接受数据的顺序一致。
Channel closed
channel关闭之后,继续向channel发送数据会panic,从channel接受数据不会panic,会立即返回,返回的是chan数据类型的零值。
ch :=make(chan int,10)
close(ch)
a :=<- ch
println(a) //print 0
//ch <- 1 //panic: send on closed channel
我们也可以通过以下代码来检查channel是否关闭
ch :=make(chan int,10)
close(ch)
a,ok :=<-ch //ok==false,表示channel被关闭
利用这个特性我们可以用来为中间的任务执行步骤的设置超时时间,超过指定时间任务没有执行完成,关闭channel,主逻辑就继续向下执行。
Channel 遍历
通过range可以遍历输出channel中的元素,channle 被close之后依然会输出channel中的元素。以下代码是向缓冲区为10的元素发送数据,然后关闭channle之后依然可以遍历输出channel里面的元素:
func main() {
c := make(chan int,10)
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
for i := range c {
fmt.Print(i)
}
}
//输出:0123456789
下面代码是向一个没有缓冲区的channel发送数据,通过range遍历从channel接受数据打印:
func main() {
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c) //必须close,否则遍历channel后会panic。会提示:fatal error: all goroutines are asleep - deadlock!
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
Channel用于select语句
select语句选择一组send或者receive操作去处理。它的case可以是send语句,也可以是receive语句,亦或default。如果case有多个channel可以接受数据,那么Go会随机选择一个case去处理,如果没有任何一个case要处理,有default的情况下会执行default逻辑,如果没有default项,则会一直阻塞,直到满足某个case条件。
下面代码是打印斐波那契数列每一项,通过在select语句之外嵌套for循环可以一直处理,直到遇到退出语句。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
打印结果
0
1
1
2
3
5
8
13
21
34
quit
Channel用于timeout
select case使用channel会一直阻塞知道满足某个case条件才会退出。不过我们可以通过time.After(timeout) 设置超时时间,超时时间到后就会自动退出。
下面代码中channel c1休眠2秒后会写入数据,然后case满足,打印result 1。
不过打印的是timeout 1,因为第二个case语句设置了超时时间。
package main
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
打印结果
timeout 1
Channle groutine同步
利用channel我们可以实现groutine之间相互通信。
下面的例子是启动一个groutine区完成任务,任务完成后想channel写入数据。
main方法收到写入数据信号就打印done,表示任务完成,否则一直阻塞知道groutine完成任务。
package main
import (
"time"
)
func worker(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// 等待任务完成
<-done
println("done")
}
参考文章:https://colobu.com/2016/04/14/Golang-Channels/
有疑问加站长微信联系(非本文作者)