golang学习笔记---channel(2)
channel容量为0和为1的区别
- 容量为1的channel是有缓冲channel的特殊情况,可以用在2个goroutine之间同步状态,或者其中一个等待另一个完成时才继续执行任务的情况。
- 无缓存的channel的容量始终为0,发送者发送数据和接受者接受数据时同时的,无任何中间态,不能缓冲任何数据。
- 容量为1的channel是可以缓冲1个数据,发送者和接受者之间可以不同时进行,可以发送者可以先把数据放进去,接受者可以过会儿再读取数据。无缓存的 channel 的发送者和接受者是相互等待,发送者等待接受者准备就绪才能发送数据,接受者等待发送者准备就绪才能接受数据,如果无缓存的 channel 在同一个协程中既发送又接受就会造成死锁而报错。
使用Range来遍历channel
使用for range来遍历channel,会自动等待channel的操作,一直到channel被关闭,退出循环。
第一个协程发送完数据之后关闭channel,使用range遍历读取channel中的数据,当channel被关闭后会退出循环结束程序
package main import ( "fmt" "time" ) func main() { //缓冲容量为3的channel c := make(chan int64, 3) //在协程中向channel中写数据 go func() { for i := 0; i < 10; i++ { time.Sleep(time.Microsecond * 100) c <- time.Now().UnixNano() } close(c) }() //通过range来打印数据,直到channel被关闭 go func() { for i := range c { time.Sleep(time.Microsecond * 1000) fmt.Println(i) } }() fmt.Scanln() //使用了fmt.Scanln()通过控制台输入扫描来hold住控制台,不让程序退出 fmt.Println("完成") }
关闭Channel
关闭channel使用了内建的函数close,对于关闭channel需要注意如下几点:
- 如果向已经关闭的channel写数据就会导致panic: send on closed channel
- 从已经关闭的channel中读取数据是不会导致panic的,可以继续读取已经发送的数据,但如果已经发送的数据读取完成时继续读取,就会会读取到类型默认值或者零值;
- 如果通过range读取数据,channel关闭后就会跳出for循环;
- 如果重复再关闭已经关闭的channel,也会导致panic。
select
- select 可以等待和处理多个通道。使用select可以在case语句中选择一组channel中未阻塞的channel。
- select只会执行一次不会循环,只会选择一个case来处理,如果要一直处理channel,通常要结合一个无限for循环一起来使用
- 在default case存在的情况下,如果没有case需要处理,则会选择default去处理;如果没有default case,则select语句会阻塞,直到某个case需要处理。
- 如果在使用for 无限循环+select来操作多个channel,当channel被关闭后,会一直读取类型默认值,这样会导致进入无限死循环
package main import ( "fmt" "time" ) func main() { //创建2个channel c1 := make(chan string) c2 := make(chan string) //通过2个协程分别往2个channel中写数据 go func() { for i := 0; i < 9; i++ { time.Sleep(100 * time.Millisecond) c1 <- fmt.Sprintf("c1: %d", i+1) } close(c1) }() go func() { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) c2 <- fmt.Sprintf("c2: %d", i+1) } close(c2) }() //通过1个协程从2个channel中读取数据,如果没有数据则阻塞 go func() { for { select { // 死循环 case msg1 := <-c1: fmt.Printf("received %d: %s \n", time.Now().Unix(), msg1) case msg2 := <-c2: fmt.Printf("received %d: %s \n", time.Now().Unix(), msg2) } } }() fmt.Scanln() }
对于读取已经关闭的channel时,可以使用返回值来判断channel是否被关闭,下面的例子中如果返回的ok为false,就说明channel已经被关闭:
Select超时
在select中可以处理超时,超时在处理外部资源或需要绑定执行时间的程序非常重要,通过channel和select,在Go中可以很容易且优雅的实现超时机制。在下面的例子使用一个协程来模拟任务处理,利用sleep 5秒钟来模拟任务执行时间,任务完成后向channel中写入结果;在select语句中实现超时,第一个case来读取channel中的数据,等待结果写入;第二个channel中使用time.After(3 * time.Second)来等待3秒超时时间。由于实际任务执行时间是5秒钟,超时时间是3秒钟,所以等待3秒钟后,time.After返回的channel中会写入一个时间,select语句就选择第二个case执行,然后结束程序:
package main import "time" import "fmt" func main() { c1 := make(chan string, 1) go func() { fmt.Println("开始时间", time.Now().Unix()) time.Sleep(5 * time.Second) c1 <- "result 1" }() select { case res := <-c1: fmt.Println(res) case <-time.After(3 * time.Second): fmt.Println("timeout 3") } fmt.Println("完成时间:", time.Now().Unix()) }
输出:
开始时间 1595297994
timeout 3
完成时间: 1595297997