网站首页 返回列表 像“草根”一样,紧贴着地面,低调的存在,冬去春来,枯荣无恙。 Golang学习笔记之select 18-09-26 10:25:01 云丶先生 161 ### 什么是Select 随着channel学习的深入,Go语言有专用于channel操作的`select`语法,与`switch`类似,非常实用,所不同的是`select`的每个`case`语句都是channel操作,具体是用于在多个发送/接收的信道操作中进行选择,`select`语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,`select`会随机地选取其中之一执行。先来个实际例子看看吧: ```go package main import ( "fmt" "time" ) func server1(ch chan string) { time.Sleep(6 * time.Second) ch <- "from server1" } func server2(ch chan string) { time.Sleep(3 * time.Second) ch <- "from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) select { case s1 := <-output1: fmt.Println(s1) case s2 := <-output2: fmt.Println(s2) } } ``` 输出: ```go from server2 ``` `select`会一直发生阻塞,除非其中有`case`准备就绪。在上述程序里,`server1`协程会在6秒之后写入`output1`信道,而`server2`协程在3秒之后就写入了`output2`信道。因此`select`语句会阻塞3秒钟,`output2`信道率先准备好,select则会选择该信道并完成执行。 ### Select的应用 在上面程序中,函数之所以取名为`server1`和`server2`,是为了展示`select`的实际应用。 假设我们有一个关键性应用,需要尽快地把输出返回给用户。这个应用的数据库复制并且存储在世界各地的服务器上。假设函数`server1`和`server2`与这样不同区域的两台服务器进行通信。每台服务器的负载和网络时延决定了它的响应时间。我们向两台服务器发送请求,并使用`select`语句等待相应的信道发出响应。`select`会选择首先响应的服务器,而忽略其它的响应。使用这种方法,我们可以向多个服务器发送请求,并给用户返回最快的响应了。 ### Default 在没有`case`准备就绪时,可以执行`select`语句中的默认情况(Default Case)。这通常用于防止`select`语句一直阻塞。 ```go package main import ( "fmt" "time" ) func process(ch chan string) { time.Sleep(10500 * time.Millisecond) ch <- "process successful" } func main() { ch := make(chan string) go process(ch) for { time.Sleep(1000 * time.Millisecond) select { case v := <-ch: fmt.Println("received value: ", v) return default: fmt.Println("no value received") } } } ``` 输出: ```go no value received no value received no value received no value received no value received no value received no value received no value received no value received no value received received value: process successful ``` 主协程启动了一个无限循环。这个无限循环在每一次迭代开始时,都会先休眠1000毫秒(1秒),然后执行一个`select`操作。由于`process`协程在10500毫秒后才会向`ch`信道写入数据,因此`select`语句的第一个`case`(即`case v := <-ch:`)并未就绪。所以在这期间,程序会执行默认情况,该程序会打印10次`no value received`。 在10.5秒之后,`process`协程会在第10行向`ch`写入`process successful`。主协程就会执行`select`语句的第一个`case`了。 ### 死锁 ```go package main func main() { ch := make(chan string) select { case <-ch: } } ``` 输出panic: ```go fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() D:/golang/1/select.go:6 +0x59 ``` 上面的程序中,我们在第4行创建了一个信道`ch`。我们在`select`内部(第6行),试图读取信道`ch`。由于没有Go协程向该信道写入数据,因此 `select`语句会一直阻塞,导致死锁。 如果存在默认情况,就不会发生死锁,因为在没有其他`case`准备就绪时,会执行默认情况。我们用默认情况重写后,程序如下: ```go package main import "fmt" func main() { ch := make(chan string) select { case <-ch: default: fmt.Println("default case executed") } } ``` 输出: ```go default case executed ``` 如果`select`只含有值为`nil`的信道,也同样会执行默认情况。 ```go package main import "fmt" func main() { var ch chan string select { case v := <-ch: fmt.Println("received value", v) default: fmt.Println("default case executed") } } ``` 输出: ```go default case executed ``` ### 随机选取 当`select`由多个`case`准备就绪时,将会随机地选取其中之一去执行。 ```go package main import ( "fmt" "time" ) func server1(ch chan string) { ch <- "from server1" } func server2(ch chan string) { ch <- "from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) time.Sleep(1 * time.Second) select { case s1 := <-output1: fmt.Println(s1) case s2 := <-output2: fmt.Println(s2) } } ``` 输出可能会是`from server1`,也可能会是`from server2`。 ### 空Select ```go package main func main() { select {} } ``` 输出: ```go fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() D:/golang/1/select.go:4 +0x27 ``` 我们已经知道,除非有`case`执行,`select`语句就会一直阻塞着。在这里,`select`语句没有任何`case`,因此它会一直阻塞并且没有任何协程的情况下,会导致死锁。该程序会触发`panic`。 **这里特别说明**,为什么有空select的存在,是因为有很多情况下需要将main函数进入永久阻塞状态,有很多办法,最常用和简便的方法就是使用`select{}`,只要在空select之前存在协程可以运行,是不会导致死锁,并且会一直阻塞。 下面举个简单常用的例子: ```go package main import ( "time" "fmt" ) func main(){ for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息 go thrind(i) } select {} // 阻塞 } func thrind( i int){ for range time.Tick(1000 * time.Millisecond) { fmt.Println("线程:",i) } } ``` 该例会创建20个协程循环输出,启动之后main程序会一直阻塞,这里就是运用了空select来阻塞,你创建的协程是一个不停息的循环状态,假如所有协程都停止了,还是会报死锁panic。比如将循环注释掉试试: ```go package main import ( "fmt" ) func main(){ for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息 go thrind(i) } select {} // 阻塞 } func thrind( i int){ //for range time.Tick(1000 * time.Millisecond) { fmt.Println("线程:",i) //} } ``` 当20个协程执行完毕,程序仍会panic: ```go fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() D:/golang/1/select.go:11 +0x5d 线程: 0 线程: 1 线程: 5 线程: 3 线程: 4 线程: 8 线程: 6 线程: 7 线程: 10 线程: 9 线程: 11 线程: 12 线程: 13 线程: 14 线程: 15 线程: 16 线程: 17 线程: 18 线程: 2 线程: 19 ``` 关键字词[Golang, Select] 分享到: 上一篇:Golang学习笔记之五大阶段 下一篇:MySQL之学习经验 如需留言,请 登录,没有账号?请 注册 0 条评论 0 人参与 最新文章 虹口区财政局2026年代理记账资格行政许可决定公示 关于公开征集网络餐饮食品安全违法违规线索的公告 2026年虹口区劳动人事争议仲裁院辅助人员招聘公告 2026年4月虹口区应急管理综合行政执法对象和执法事项公示 虹口区财政局 2026 年代理记账资格行政许可决定公示 上海市虹口区人民政府关于上海港国际客运中心口岸限定区域范围及管理要求的通告 虹口区98街坊HK381-05地块新建学校项目方案公示公告 虹口区广中社区N080601单元控制性详细规划077街坊局部调整公众参与草案补充公示 点击排行 站点又重新回来了,必须纪念一下本次事故 Beego框架第1节——环境与初始 Golang学习笔记之interface leetcode基础算法学习之addTwoNumbers Golang学习笔记之匿名函数与闭包 最新评论 efg 7年前 不好意思,胡乱写了一条评论 efg 7年前 aaaa kakafu 7年前 good 云丶先生 官方 7年前 你好,可以,麻烦你的站点做好友链 云丶先生 官方 7年前 欢迎各界人士留言,请不要胡乱填写,尊重他人就是尊重... 友情链接 四方支付系统,聚合支付系统,三方支付系统,支付系统源码 支付系统开发,四方支付系统,支付系统源码
0 条评论 0 人参与