package main import ( "fmt" "time" ) func main() { c1 := make(chan string) c2 := make(chan string) c3 := make(chan string) var c4 chan string c5 := make(chan string) close(c5) c6 := make(chan string, 3) c6 <- "buffered 1" c6 <- "buffered 2" c6 <- "buffered 3" close(c6) go func() { time.Sleep(time.Second * 1) c1 <- "one" close(c1)//关闭c1 channel }() go func() { time.Sleep(time.Second * 2) c2 <- "two" }() go func() { fmt.Println(<-c3) }() for { select { case msg1,ok := <-c1: if ok { fmt.Println("received1 ", msg1) } else { c1 = nil //判断c1 channel已经关闭后,将其赋值为nil,则此case将一直阻塞,故此select继续评估其它case,避免了死循环, fmt.Println("c1 channel has closed.") } case msg2 := <-c2: //从c2 channel中读 fmt.Println("received2 ", msg2) case c3 <- "write string": //向c3 channel中写 fmt.Println("c3 channel wrote.") case <- c4: //因为c4 is nil, 所以此case将一直阻塞,故select继续评估其它case. fmt.Println("never reach here.") case c4 <- "never": //因为c4 is nil, 所以此case将一直阻塞,故select继续评估其它case. fmt.Println("never reach here.") case str := <- c5: //死循环,不断地读取出零值,即空字符串。 fmt.Printf("empty: %s", str) case c5 <- "panic": //向已经关闭的channel写,发生panic异常。 fmt.Println("panic") case str := <- c6://死循环,在读取完c6中已写入的值之后,则继续不断地读取出零值,即空字符串。 fmt.Printf("buffered channel value: %s",str) case c6 <- "buffered channel closed, write to panic":
//向已经关闭的channel写,发生panic异常。 fmt.Println("buffered channel closed, write to panic")
case <- time.After(time.Second * 5): //定时器,防止操作无限期阻塞 fmt.Println("timeout\n") //default: //无default则select为阻塞等待的; 有default则为非阻塞等待的,好像spin-lock. // fmt.Println("default\n") } } }
总结: select中的每一个case 都是针对channel的IO操作,读或写,当有多个case同时就绪(可读可写)时,select会随机选择一个执行。
select{}将会永远阻塞;通常select与for并用,这样select就可以循环不断地评估每一个case,随机选取一个case执行。
其实select这个概念在linux中早就出现了, 用于监视多个文件描述符是是否有IO操作,当有文件描述符发生IO操作时,select调用立即返回,返回值中包括发生IO操作的文件描述符集合。
参考:http://tonybai.com/2014/09/29/a-channel-compendium-for-golang/
注意: 此文章只是我个人笔记, 如有错漏,请一定指正, 共同学习, 我的邮箱: htyu_0203_39@sina.com
有疑问加站长微信联系(非本文作者)