- 原文地址:Part 24: Select
- 原文做者:Naveen R
- 译者:咔叽咔叽 转载请注明出处。
select
语句用于从多个发送/接收channel
中进行选择的操做。 select
语句将阻塞直到其中一个发送/接收操做准备就绪。若是有多个操做就绪,则随机选择其中一个操做。语法相似于switch
,只是每一个case
语句被一个channel
操做取代了。让咱们深刻研究一些代码,以便更好地理解golang
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)
}
}
复制代码
在上面的程序中,在第 8 行server1
函数休眠 6 秒而后将文本从server1
写入channel ch
。第 12 行server2
函数休眠 3 秒,而后从server2
写入channel ch
。服务器
main
函数在 20 和 21 行分别调用server1
和server2
。网络
在第 22 行,select
语句将阻塞直到其中一个case
准备就绪。在上面的程序中,server1
在 6 秒后写入output1 channel
,而server2
在 3 秒后写入output2 channel
。所以 select 语句将阻塞 3 秒并等待server2
写入。 3 秒后,程序将打印,并发
from server2
复制代码
而后终止。函数
select
的用途将上述程序中的函数命名为server1
和server2
的缘由是为了说明select
的实际用途。ui
让咱们假设咱们有一个关键任务的应用,咱们须要尽快将输出返回给用户。该应用程序的数据库被复制并存储在世界各地的不一样服务器中。假设函数server1
和server2
实际上与 2 个这样的服务器通讯。每一个服务器的响应时间取决于每一个服务器的负载和网络延迟。咱们将请求发送到两个服务器,而后使用select
语句在相应的channel
上等待响应。select
会选择优先响应的服务器,其余响应被忽略。这样咱们就能够向多个服务器发送相同的请求,并将最快的响应返回给用户:)。spa
case
当其余case
都没有准备就绪时,将会执行select
语句中的默认case
。这一般用于防止select
语句阻塞。code
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")
}
}
}
复制代码
Run in playgroundserver
在上面的程序中,在第 8 行process
函数休眠 10500 毫秒(10.5 秒),而后将process successful
写入ch channel
。该函数在第 15 行被并发调用。
在并发调用process Goroutine
以后,main Goroutine
中启动了无限循环。无限循环在每次迭代开始期间休眠 1000 毫秒(1 秒),并执行select
操做。在前 10500 毫秒期间,select
语句的第一种状况即case v:= <-ch:
将不会准备就绪,由于process Goroutine
仅在 10500 毫秒后才写入ch channel
。所以,在此期间将执行defualt
分支,程序将会打印 10 次no value received
。
在 10.5 秒以后,process Goroutine
将process successful
写入ch
。 如今将执行select
语句的第一种状况,程序将打印received value: process successful
而后程序终止。该程序将输出,
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
复制代码
case
package main
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
复制代码
在上面的程序中,咱们在第一行建立了一个channel ch
。咱们尝试从选择的这个channel
读取。而这个select
语句将一直阻塞,由于没有其余Goroutine
写入此channel
,所以将致使死锁。该程序将在运行时产生panic
同时打印,
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox416567824/main.go:6 +0x80
复制代码
若是存在默认case
,则不会发生此死锁,由于在没有其余case
准备就绪时将执行默认case
。上面的程序能够重写。
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case <-ch:
default:
fmt.Println("default case executed")
}
}
复制代码
输出,
default case executed
复制代码
相似地,当select
只有一个nil channel
,也会执行默认case
。
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")
}
}
复制代码
在上面的程序中,ch
是nil
,咱们试图用select
从ch
中读取。若是没有默认case
,则select
将一直被阻塞并致使死锁。因为咱们在select
中有一个默认的case
,它将被执行而且程序将打印,
default case executed
复制代码
select
的随机性当select
语句中的多个case
准备就绪时,将会随机挑选一个执行。
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)
}
}
复制代码
在上面的程序中,server1
和server2
协程在第 18 和 19 行分别被调用,而后main
协程休眠 1 秒。当运行到select
语句时,server1
已将from server1
写入output1
,server2
已将from server2
写入output2
,所以select
语句中的两种状况都准备就绪。若是屡次运行此程序,将会随机输出from server1
或from server2
。
select
package main
func main() {
select {}
}
复制代码
你认为上面的程序将会输出什么?
咱们知道select
语句将被阻塞,直到执行其中一个case
。在这种状况下,select
语句没有任何case
,所以它将一直阻塞致使死锁。这个程序将会产生panic
,并输出,
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/tmp/sandbox299546399/main.go:4 +0x20
复制代码