select和switch。让我们回顾一下switch语句。在switch语句中,case语句(可以是值也可以是表达式)会一一匹配,一一判断过去。,直到有匹配的语句,执行完匹配的语句内容后跳出switch。funcdemo(numberint){switch{casenumber>=90:fmt.Println("Excellent")default:fmt.Println("Toorub")}}和select用于处理通道,其语法与switch非常相似。每个case语句必须是一个通道操作。它既可以用于通道数据接收,也可以用于通道数据传输。funcfoo(){chanInt:=make(chanint)deferclose(chanInt)gofunc(){select{casedata,ok:=<-chanInt:ifok{fmt.Println(data)}default:fmt.Println("allblocked")}}()chanInt<-1}Output1这是一个简单的接收和发送模型。如果select的多个分支满足条件,则随机选择其中一个满足条件的分支。将ok添加到第6行,因为如上一节所述,如果不添加它,当通道关闭时,您将收到一个零值。?如果发送太慢,所有case都会被阻塞,直接执行default的内容。这里加一行sleep试试。funcbar(){chanInt:=make(chanint)deferclose(chanInt)gofunc(){....}()time.Sleep(time.Second)chanInt<-1}倒数第二行加入sleep1秒,导致如果select语句提前结束,猜猜它是否会输出所有块?所有块fataleror:allgoroutinesareasleep-deadlock!goroutine1[chansend]:main.bar()将输出所有块。因为接收执行完,goroutine退出,而发送刚刚执行完,没有匹配的接收,所以就死锁了。正确的做法是将接收包裹在循环中。funcbaz(){chanInt:=make(chanint)deferclose(chanInt)gofunc(){for{select{...}}}()chanInt<-1}不再死锁如果程序没有停止,就会有一个leakgoroutine不可能永远跳出for循环。这时,下一节介绍的内容通知机制总是简单灵活的。虽然没有专门处理退出的机制,但是我们可以自己组合funcmain()。{chanInt,done:=make(chanint),make(chanstruct{})deferclose(chanInt)deferclose(done)gofunc(){for{select{case<-chanInt:case<-done:break}}}()done<-struct{}{}}不向chanInt发送任何东西,阻塞是合理的,导致goroutine泄漏,但是可以使用额外的通道来完成协程的退出控制。这个方法也可以做周期性的处理任务,下一节我们详细讲解并发案例。案例有并发属性,比如两个输入,分别等待1秒和2秒,然后读取两次,会不会需要3秒?funcmain(){c1,c2:=make(chanstring),make(chanstring)close(c1)close(c2)gofunc(){time.Sleep(time.Second*1)c1<-"one"}()gofunc(){time.Sleep(time.Second*2)c2<-"two"}()start:=time.Now()//获取当前时间fori:=0;i<2;i++{select{case<-c1:case<-c2:}}elapsed:=time.Since(start)//这里没有用到3秒,为什么?fmt.Println("Time-consumingtoexecutethisfunction:",elapsed)}上面的代码首先初始化了两个通道c1和c2,然后启动两个goroutine分别向c1和c2写入数据,然后通过select监听两个通道,从中读取数据并输出。运行结果如下:$gorunchannel.goreceivedonereceivedtwo执行该函数耗时:2.004695535s这充分说明case是并发的,但是需要注意的是这里的并发是对channelblockingbycase的特殊处理.case并发的原则如果case的左右两边跟在函数后面,函数就会执行。让我们来探讨一下。定义A和B函数,功能相同funcA()int{fmt.Println("startA")time.Sleep(1*time.Second)fmt.Println("endA")return1}告诉我函数是否执行需要多少时间?funclee(){ch,done:=make(chanint),make(chanstruct{})deferclose(ch)gofunc(){select{casech<-A():casech<-B():case<-done:}}()done<-struct{}{}}答案是2秒startAendAstartBendBmain.leespendtime:2.003504395s选择扫描是从左到右,从上到下,按这个顺序先求值,如果是函数函数就会先执行。然后立即判断是否可以立即执行(这里是case是否会因为执行而阻塞)。所以两个函数都会进入,而且先进入A再进入B,两个函数都会执行,这样等待的时间就会累积。如果没有blocking,此时会用伪随机算法选出一个case,只要选到另一个就丢弃。超时控制我们来模拟一个比较现实的例子,让程序在超时一段时间后退出。定义一个结构体typeWorkerstruct{stream<-chanint//processingtimeouttime.Duration//timeoutdonechanstruct{}//endsignal}定义初始化函数funcNewWorker(stream<-chanint,timeoutint)*Worker{return&Worker{stream:stream,timeout:time.Duration(timeout)*time.Second,done:make(chanstruct{}),}}定义超时处理函数func(w*Worker)afterTimeStop(){gofunc(){time.Sleep(w.timeout)w。done<-struct{}{}}()}随时间发送结束信号,接收数据并处理函数func(w*Worker)Start(){w.afterTimeStop()for{select{casedata,ok:=<-w.stream:if!ok{return}fmt.Println(data)case<-w.done:close(w.done)return}}}收到结束信号后关闭函数该方法让程序在等待1后继续second执行,程序不会因ch读取等待而停止。funcmain(){stream:=make(chanint)deferclose(stream)w:=NewWorker(stream,3)w.Start()}实际上需要3秒直到程序完成运行。这个方法巧妙的实现了超时处理机制。这种方法不仅简单,而且在实际项目开发中也非常实用。小结本节介绍select的用法和陷阱。我们了解到该案例是并发的,该案例只是对通道传输阻塞做了特殊处理。如果有计算,则先计算。扫描是从左到右,从上到下。按照这个顺序先求值,如果是函数,则先执行函数。如果函数运行时间较长,所有case都阻塞时时间会累积,执行default中的内容。可以使用结束信号让select退出,延时发送结束信号,实现超时自动退出的功能。问题:为什么w.stream没有程序发送他发送数据,但是没有死锁?本节源码位置https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/4.5-select》本文转载自微信公众号《机智的“程序员熊”》,您可以通过以下二维码关注。转载本文请联系机智的程序员小熊公众号。
