[譯] 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
複製代碼
相關文章
相關標籤/搜索