Go語言併發模型:使用 select

簡介

做爲一種現代語言,go語言實現了對併發的原生支持。上幾期文章中,咱們對goroutine 和 channel進行了詳細的講解。可是要實現對 channel 的控制,從語言層面上來講,select 語句是必不可少的部分。本文中,咱們就 select 語句的行爲和使用方法進行深刻討論。git

閱讀建議

本文中的內容是 Go語言併發模型的一篇,可是與上幾期關係不是特別密切,能夠獨立閱讀。本文的內容源自於 go language specifications 和 Rob Pike 在2012年進行的一場名爲"concurrency" 的演講。若是有時間的話,建議在 YouTube 上看一下他本人的演講。github

select 語句的行爲

爲了便於理解,咱們首先給出一個代碼片斷:golang

// https://talks.golang.org/2012/concurrency.slide#32
select {
case v1 := <-c1:
    fmt.Printf("received %v from c1\n", v1)
case v2 := <-c2:
    fmt.Printf("received %v from c2\n", v1)
case c3 <- 23:
    fmt.Printf("sent %v to c3\n", 23)
default:
    fmt.Printf("no one was ready to communicate\n")
}

上面這段代碼中,select 語句有四個 case 子語句,前兩個是 receive 操做,第三個是 send 操做,最後一個是默認操做。代碼執行到 select 時,case 語句會按照源代碼的順序被評估,且只評估一次,評估的結果會出現下面這幾種狀況:編程

  1. 除 default 外,若是隻有一個 case 語句評估經過,那麼就執行這個case裏的語句;c#

  2. 除 default 外,若是有多個 case 語句評估經過,那麼經過僞隨機的方式隨機選一個;微信

  3. 若是 default 外的 case 語句都沒有經過評估,那麼執行 default 裏的語句;併發

  4. 若是沒有 default,那麼 代碼塊會被阻塞,指導有一個 case 經過評估;不然一直阻塞編程語言

若是 case 語句中 的 receive 操做的對象是 nil channel,那麼也會阻塞,下面咱們看一個更全面、用法也更高級的例子:ide

// https://golang.org/ref/spec#Select_statements
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
    print("received ", i1, " from c1\n")
case c2 <- i2:
    print("sent ", i2, " to c2\n")
case i3, ok := (<-c3):  // same as: i3, ok := <-c3
    if ok {
        print("received ", i3, " from c3\n")
    } else {
        print("c3 is closed\n")
    }
case a[f()] = <-c4:
    // same as:
    // case t := <-c4
    //    a[f()] = t
default:
    print("no communication\n")
}

for {  // 向 channel c 發送隨機 bit 串
    select {
    case c <- 0:  // note: no statement, no fallthrough, no folding of cases
    case c <- 1:
    }
}

select {}  // 永久阻塞

注意:與 C/C++ 等傳統編程語言不一樣,go語言的 case 語句不須要 break 關鍵字去跳出 select。函數

select 的使用

爲請求設置超時時間

在 golang 1.7 以前, http 包並無引入 context 支持,經過 http.Client 向一個壞掉的服務發送請求會致使響應緩慢。相似的場景下,咱們可使用 select 控制服務響應時間,下面是一個簡單的demo:

func main() {
    c := boring("Joe")
    timeout := time.After(5 * time.Second)
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-timeout:
            fmt.Println("You talk too much.")
            return
        }
    }
}

done channel

上幾期的文章中,咱們均討論過 done channel,它能夠用於保證流水線上每一個階段goroutine 的退出。在 golang.org/x/net 包中,done channel 被普遍應用。這裏咱們看一眼 golang.org/x/net/context/ctxhttp 中 Do 方法的實現:

// https://github.com/golang/net/blob/release-branch.go1.7/context/ctxhttp/ctxhttp.go

// Do sends an HTTP request with the provided http.Client and returns
// an HTTP response.
//
// If the client is nil, http.DefaultClient is used.
//
// The provided ctx must be non-nil. If it is canceled or times out,
// ctx.Err() will be returned.
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
    if client == nil {
        client = http.DefaultClient
    }
    resp, err := client.Do(req.WithContext(ctx))
    // If we got an error, and the context has been canceled,
    // the context's error is probably more useful.
    if err != nil {
        select {
        case <-ctx.Done():
            err = ctx.Err()
        default:
        }
    }
    return resp, err
}

quit channel

在不少場景下,quit channel 和 done channel 是一個概念。在併發程序中,一般 main routine 將任務分給其它 go routine 去完成,而自身只是起到調度做用。這種狀況下,main 函數沒法知道 其它goroutine 任務是否完成,此時咱們須要 quit channel;對於更細粒度的控制,好比完成多少,仍是須要 done channel (參考WaitGroup)。 下面是 quit channel 的一個例子,首先是 main routine:

// 建立 quit channel
quit := make(chan string)
// 啓動生產者 goroutine
c := boring("Joe", quit)
// 從生產者 channel 讀取結果
for i := rand.Intn(10); i >= 0; i-- { fmt.Println(<-c) }
// 經過 quit channel 通知生產者中止生產
quit <- "Bye!"
fmt.Printf("Joe says: %q\n", <-quit)

咱們再看 生產者 go routine 中與 quit channel 相關的部分:

select {
case c <- fmt.Sprintf("%s: %d", msg, i):
    // do nothing
case <-quit:
    cleanup()
    quit <- "See you!"
    return
}

Google Search (延伸閱讀)

Google Search 是一個很經典的例子,因爲代碼較多,有興趣的童鞋查看 Rob Pike 的 ppt
更高階的併發方式能夠閱讀 Sameer Ajmani 的 ppt Advanced Go Concurrency Patterns

併發相關的主題就先到這裏,下一期文章中,咱們會討論go語言測試工具鏈中的單元測試。

相關連接:

  1. Rob Pike演講:concurrency

  2. language specification: select statement

掃碼關注微信公衆號「深刻Go語言」

在這裏

相關文章
相關標籤/搜索