网站首页 返回列表 像“草根”一样,紧贴着地面,低调的存在,冬去春来,枯荣无恙。 Golang学习笔记之select 23-09-26 10:25:01 字节波 334 ### 什么是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 人参与 最新文章 Dapp合约开发指南 ansible学习记录-远程开启exe不能挂起UI界面 leetcode基础算法学习之maxArea leetcode基础算法学习之ReverseInt leetcode基础算法学习之LongestSubstr leetcode基础算法学习之addTwoNumbers leetcode基础算法学习之FindIndex CentOS7安装nginx服务 点击排行 优雅的语言开发优雅的站点 Beego框架第1节——环境与初始 Golang学习笔记之匿名函数与闭包 Golang学习笔记之interface Dapp合约开发指南 最新评论 字节波 官方 1年前 你好,可以,麻烦你的站点做好友链 字节波 官方 1年前 欢迎各界人士评论留言,注意要遵守法律法规,祝每一位... 友情链接 BYTE STUDIO 字节波 ByteWave 360导航 360安全浏览器
0 条评论 0 人参与