隨著區塊鏈引發的“顛覆風暴”,大量區塊鏈培訓機構應運而生。但在魚龍混雜的培訓圈內,要想找到真正符合標準的課程體系與專屬區塊鏈領域的專業授課講師簡直是滄海一粟。兄弟連教育指出,是時候做出行動改變並顛覆傳統培訓機構運營思維,並提醒福士使用者,應理性選擇區塊鏈培訓機構。
作為一種現代語言,go語言實現了對並發的原生支援。
select 語句的行為
為了便於理解,我們首先給出一個程式碼片段:
// 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 語句會按照原始碼的順序被評估,且只評估一次,評估的結果會出現下面這幾種情況:
除 default 外,如果只有一個 case 語句評估通過,那麼就執行這個case裡的語句;
除 default 外,如果有多個 case 語句評估通過,那麼通過偽隨機的方式隨機選一個;
如果 default 外的 case 語句都沒有通過評估,那麼執行 default 裡的語句;
如果沒有 default,那麼 代碼塊會被阻塞,指導有一個 case 通過評估;否則一直阻塞
如果 case 語句中 的 receive 操作的對象是 nil channel,那麼也會阻塞,下面我們看一個更全面、用法也更進階的例子:
// 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
// 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 。 下面是 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
}