1、selectgit
Go语言引入了select关键字,其语法与switch很是相似,先看一个switch例子:
编程
func main() {并发 var a int = 1ide switch {函数 case a == 1:ui fmt.Println("ok")lua case a == 2:spa fmt.Println("no ok")命令行 default:3d fmt.Println("default") } } |
运行该程序,能够正常打印出“ok”
下面把switch替换为select:
func main() { var a int = 1 select{ case a == 1: fmt.Println("ok") case a == 2: fmt.Println("no ok") default: fmt.Println("default") } } |
运行该程序抛出错误:a == 1 evaluated but not used
这是为什么?是由于select中的case语句必须是一个IO操做!!!
即:switch中的case语句判断条件只要能比较就行,而select中的case语句判断条件必须是一个IO操做。
咱们在《Go语言的并发》中说过Channel,从Channel中读取值和向Channel中写入值对应的操做都是IO操做。下面咱们写一个关于select的例子:
func setValue(ch1 chan int, ch2 chan string) { ch1 <- 1 ch2 <- "goroutine" } |
定义一个函数setValue(),入参为两个channel,该方法体内向channel 1中写入一个×××值、向channel 2中写入一个字符串;在并发章节咱们说过:“当入觉得channel时,就不是值传递了,而变成一个地址传递”。
func main() { var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") default: fmt.Println("default") } } |
先定义两个channel类型的变量,使用make对其初始化;而后使用go关键字拉启一个goroutine;main所在goroutine继续向下走,开始执行select。
执行一下程序:
发现只打印了一个“default”,而没有打印“ch1 ok”或者“ch2 ok”,多执行几回结果同样,从并发的角度上考虑几率状况,这是不正常的。
您可能会想向ch一、ch2中写入数据,属于IO操做,可能会慢一些,当select执行完default时,IO操做依旧没有完成。咱们验证一下,修改代码:
func main() { var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
这里把default删除掉了,当执行到select时,程序会查看各个分支,因为没有default分支,此时若channel中没有内容,则main所在的goroutin会阻塞,至到ch1或者ch2中有内容为至。
执行一下该程序:
发现成功打印出"ch1 ok”,多运行几回依旧打印出“ch1 ok”,从没有打印出“ch2 ok”,这是为何呢?
看过我前面章节的可能会想,因为目前使用的是go1.4版本,它并不支持多核并发,加之GO语言在执行时更倾向先让一个goroutine执行完(即咱们常说的让领导先走:) ),下面咱们再修改一下程序:
func main() { runtime.GOMAXPROCS(runtime.NumCPU()) // 强制Go进行多核并发 var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
再多运行几回程序:
从运行结果上来看,多核也没有解决这个问题,这是由于当执行到select时,它发现ch一、ch2均无内容,程序发生阻塞,直到另外的goroutine把数据写入ch1或ch2,因为在另外的goroutine中ch1老是第一个被写入数据,因此main所在的gorouinte老是先从ch1中获取到数据,从而打印“ch1 ok”以后就退出了!
那如何完善这个程序呢?
func setValue(ch chan int){ ch <- 1 } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) var ch1, ch2 chan int = make(chan int), make(chan int) go setValue(ch1) go setValue(ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
多运行几回看一下结果:
从结果上来看,达到了预期目的!
2、死锁
如上面的setValue()方法所示,入参是一个channel,方法体是向该channel中写入数据,因为channel做为入参是一个地址传递,因此在select中的case始终能从channel中读取到数据。
试想若程序猿把setValue()中赋值忘记了呢?以下:
func setValue(ch chan int) { // ch <- 1 注释掉该行 } |
运行一下发现系统报了死锁:
像这种状况是在所不免的,若是避免死锁这类问题呢?
有一种办法是引入另外一个超时Channel,另启一个goroutine先让它休息必定时间(超时时间),而后把数据写入该Channel,代码以下:
package main import ( "fmt" "runtime" "time" ) func setValue(ch chan int) { //ch <- 1 让ch一、ch2产生死锁 } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) var timeout chan bool = make(chan bool) // 建立一个超时的Channel go func() { // 新建立一个goroutine time.Sleep(time.Second * 10) // 休息10秒 timeout <- true // 在10秒内ch一、ch2若尚未向里面写入数据,则认为超时 }() // 加一个()的意思是让这个gorouinte执行 var ch1, ch2 chan int = make(chan int), make(chan int) go setValue(ch1) go setValue(ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") case <-timeout: // 若ch一、ch2死锁,10秒钟后timeout填充数据,避免死锁 fmt.Println("Timeout coming...") } } |
运行一下程序:
3、有意思的程序
啥话都别说了,直接上代码:
package main import ( "fmt" ) func main() { var ch chan int = make(chan int, 1) for { select { case ch <- 0: case ch <- 1: } fmt.Printf("%d", <-ch) } } |
看懂了没有?
解释一下:
一、先定义一个类型为int的Channel
二、再来一个死循环
三、使用select关键字进行选择
四、case的后面是分别向ch写入0或者1
五、使用Printf进行打印结果
在命令行窗口或者git中执行一下该程序,结果以下:
打印出一连串的随机数
这是为何呢?
select {
case ch <- 0:
case ch <- 1:
}
这句话的意思就是向channel中放置数据为0或者1的随机数.