1. 程式人生 > >golang教程之Select

golang教程之Select

Select

什麼是Select?

select語句用於從多個傳送/接收通道操作中進行選擇。 select語句將阻塞,直到其中一個傳送/接收操作準備就緒。 如果準備好多個操作,則隨機選擇其中一個操作。語法類似於switch,除了每個case語句都是一個通道操作。 讓我們深入瞭解一些程式碼以便更好地理解。

例子

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

在上面的程式中,server1函式中睡眠6秒然後將文字從server1寫入通道ch。 server2的功能是睡眠3秒,然後從server2寫入通道ch。

main函式呼叫go no Gorosines server1server2

第22行中控制器到達select語句。 select語句將阻塞,直到其中一個case準備就緒。 在上面的程式中,server1 Goroutine在6秒後寫入output1通道,而server2在3秒後寫入output2通道。 因此select語句將阻塞3秒並等待server2

Goroutine寫入output2通道。 3秒後,程式打印出來,

from server2 

然後將終止。

實際使用Select

將上述程式中的函式命名為server1server2的原因是為了說明select的實際用途。

讓我們假設我們有一個關鍵任務應用程式,我們需要儘快將輸出返回給使用者。該應用程式的資料庫被複制並存儲在世界各地的不同伺服器中。假設函式server1server2實際上與2個這樣的伺服器通訊。每個伺服器的響應時間取決於每個伺服器的負載和網路延遲。我們將請求傳送到兩個伺服器,然後使用select語句在相應的通道上等待響應。首先響應的伺服器由select選擇,其他響應被忽略。這樣我們就可以向多個伺服器傳送相同的請求,並將最快的響應返回給使用者。

預設case

當其他case都沒有準備就緒時,將執行select語句中的預設情況。這通常用於防止select語句阻塞。

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

}

在上面的程式中,process函式睡眠10500毫秒(10.5秒),然後將處理成功寫入ch通道。此函式在第15行中同時呼叫。

在同時呼叫processGoroutine之後,在主Goroutine中啟動了無限迴圈。無限迴圈在每次迭代開始期間休眠1000毫秒(1秒),並執行select操作。在前10500毫秒期間,select語句的第一種情況即case v:= <-ch:將不會準備就緒,因為processGoroutine將僅在10500毫秒之後寫入ch通道。因此,在此期間將執行預設情況,程式將不會列印10次接收的值。

在10.5秒之後,processGoroutine將process成功寫入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:
    }
}

在上面的程式中,我們在第4行建立了一個通道ch。 我們試著在第6行中選擇這個通道。 select語句將永遠阻塞,因為沒有其他Goroutine寫入此通道,因此將導致死鎖。 該程式將在執行時出現以下訊息而發生混亂,

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox416567824/main.go:6 +0x80

如果存在預設情況,則不會發生此死鎖,因為在沒有其他情況準備就緒時將執行預設情況。 上面的程式用下面的預設情況重寫。

package main

import "fmt"

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    default:
        fmt.Println("default case executed")
    }
}

以上程式將列印,

default case executed  

類似地,即使select只有nil通道,也會執行預設情況。

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。如果預設情況不存在,則select將永遠被阻塞並導致死鎖。 由於我們在select中有一個預設的情況,它將被執行並且程式將被列印,

default case executed  

隨機選擇

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

在上面的程式中,server1server2 go例程在第18和19行中呼叫。然後主程式在第20行中休眠1秒。 當控制器到達第21行中的select語句時。server1將從server1寫入output1通道,server2將從server2寫入output2通道,因此select語句的兩種情況都準備好執行。 如果多次執行此程式,輸出將在server1server2之間變化,具體取決於隨機選擇的情況。

請在本地系統中執行此程式以獲得此隨機性。 如果該程式在playground上執行,它將列印相同的輸出,因為playground是確定性的。

常見問題 - 空選擇

package main

func main() {  
    select {}
}

您認為上述程式的輸出結果如何?

我們知道select語句將被阻塞,直到其中一個case被執行。 在這種情況下,select語句沒有任何情況,因此它將永遠阻塞,從而導致死鎖。 這個程式會因以下輸出而感到恐慌,

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:  
main.main()  
    /tmp/sandbox299546399/main.go:4 +0x20