[译] part24: golang select

什么是 select

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)
    }
}
复制代码

Run in playgroud数据库

在上面的程序中,在第 8 行server1函数休眠 6 秒而后将文本从server1写入channel ch。第 12 行server2函数休眠 3 秒,而后从server2写入channel ch服务器

main函数在 20 和 21 行分别调用server1server2网络

在第 22 行,select语句将阻塞直到其中一个case准备就绪。在上面的程序中,server1在 6 秒后写入output1 channel,而server2在 3 秒后写入output2 channel。所以 select 语句将阻塞 3 秒并等待server2写入。 3 秒后,程序将打印,并发

from server2
复制代码

而后终止。函数

select 的用途

将上述程序中的函数命名为server1server2的缘由是为了说明select的实际用途。ui

让咱们假设咱们有一个关键任务的应用,咱们须要尽快将输出返回给用户。该应用程序的数据库被复制并存储在世界各地的不一样服务器中。假设函数server1server2实际上与 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 Goroutineprocess 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:
    }
}
复制代码

Run in playgroud

在上面的程序中,咱们在第一行建立了一个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")
    }
}
复制代码

Run in playground

输出,

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")

    }
}
复制代码

Run in playground

在上面的程序中,chnil,咱们试图用selectch中读取。若是没有默认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)
    }
}
复制代码

Run in playground

在上面的程序中,server1server2 协程在第 18 和 19 行分别被调用,而后main协程休眠 1 秒。当运行到select语句时,server1已将from server1写入output1server2已将from server2写入output2,所以select语句中的两种状况都准备就绪。若是屡次运行此程序,将会随机输出from server1from server2

select

package main

func main() {
    select {}
}
复制代码

Run in playground

你认为上面的程序将会输出什么?

咱们知道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
复制代码
相关文章
相关标签/搜索