Channel
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。 Channel可以作为一个先入先出(FIFO)的队列,接收的数据和发送的数据的顺序是一致的。
Channel的创建
channel必须先创建再使用: ch := make(chan int)
或者 make(chan int, 100)
, 否则会永久阻塞。
Channel类型 ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
,可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
ch := make ( chan int )
ch <- v
v := <- ch
chan T
chan <- float64
<- chan int
Channel有无容量的区别(缓存)
容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。 如果没有设置容量,或者容量设置为0, 说明Channel
没有缓存,只有sender
和receiver
都准备好了后它们的通讯(communication)才会发生(Blocking)。 如果设置了缓存,就有可能不发生阻塞, 只有buffer
满了后 send
才会阻塞, 而只有缓存空了后receive
才会阻塞。一个nil channel
不会通信。 通过 len 函数可以获得 chan 中的元素个数,通过 cap 函数可以得到 channel 的缓存长度。
range 遍历
channel 也可以使用 range
取值,并且会一直从 channel
中读取数据,直到有 goroutine
对改 channel
执行 close
操作,循环才会结束。
ch := make ( chan int , 10 )
for x := range ch{
fmt. Println ( x)
}
关闭 Channel
golang 提供了内置的 close
函数对 channel
进行关闭操作。 关闭一个未初始化(nil
) 的 channel
会产生 panic
重复关闭同一个 channel
会产生 panic
向一个已关闭的 channel
中发送消息会产生 panic
从已关闭的 channel
读取消息不会产生 panic
,且能读出 channel
中还未被读取的消息,若消息均已读出,则会读到类型的零值。 从一个已关闭的 channel
中读取消息永远不会阻塞,并且会返回一个为 false
的 ok-idiom
,可以用它来判断 channel
是否关闭 关闭 channel
会产生一个广播机制,所有向 channel
读取消息的 goroutine
都会收到消息
ch := make ( chan int )
close ( ch)
x, ok := <- ch
fmt. Println ( x, ok)
select
select
语句选择一组可能的send
操作和receive
操作去处理。它类似switch
,但是只是用来处理通讯(communication)操作。
它的case
可以是send
语句,也可以是receive
语句,亦或者default
。 如果有同时多个 case
去处理,那么Go会伪随机的选择一个case
处理(pseudo-random)。 如果没有case
需要处理,则会选择default
去处理,如果default case
存在的情况下。 如果没有default case
,则select
语句会阻塞,直到某个case
需要处理。 receive
语句可以将值赋值给一个或者两个变量。它必须是一个receive
操作。最多允许有一个default case
,它可以放在case
列表的任何位置,尽管我们大部分会将它放在最后。 nil channel上的操作会一直被阻塞,如果没有default case,只有nil channel的select会一直被阻塞。
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)
}
select
语句和switch
语句一样,它不是循环,它只会选择一个case
来处理,如果想一直处理channel
,你可以在外面加一个无限的for循环:
for {
select {
case c <- x:
x, y = y, x+ y
case <- quit:
fmt. Println ( "quit" )
return
}
}
超时处理
select
有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case
需要处理,select
语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。
time.After
方法,它返回一个类型为<-chan Time
的单向的channel
,在指定的时间发送一个当前时间给返回的channel中。 下面这个例子我们会在2秒后往channel c1
中发送一个数据,但是select
设置为1秒超时,因此我们会打印出timeout 1
,而不是result 1。
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" )
}
}
Timer和Ticker
timer
是一个定时器,代表未来的一个单一事件,你可以告诉timer
你要等待多长时间,它提供一个Channel
,在将来的那个时间那个Channel
提供了一个时间值。
timer1 := time. NewTimer ( time. Second * 2 )
<- timer1. C
fmt. Println ( "Timer 1 expired" )
当然如果只是想单纯的等待的话,可以使用time.Sleep
来实现。还可以使用timer.Stop
来停止计时器。
timer2 := time. NewTimer ( time. Second)
go func ( ) {
<- timer2. C
fmt. Println ( "Timer 2 expired" )
} ( )
stop2 := timer2. Stop ( )
if stop2 {
fmt. Println ( "Timer 2 stopped" )
}
ticker
是一个定时触发的计时器,它会以一个间隔interval
往Channel
发送一个事件(当前时间),而Channel
的接收者可以以固定的时间间隔从Channel
中读取事件。
ticker := time. NewTicker ( time. Millisecond * 500 )
go func ( ) {
for t := range ticker. C {
fmt. Println ( "Tick at" , t)
}
} ( )
timer, ticker
也可以通过Stop
方法来停止。一旦它停止,接收者不再会从channel
中接收数据了。