Go 部落格
Go 並行模式:計時結束,繼續進行
並行程式設計有其慣用語法。超時便是個很好的例子。儘管 Go 的通道不直接支援超時,但它們很容易實作。假設我們想從通道 ch
接收,但只願意等候值最多一秒到來。我們會從建立信號通道和發起一個睡後才會在通道上傳送的 goroutine 開始
timeout := make(chan bool, 1)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
接著,我們可以使用 select
陳述式從 ch
或 timeout
接收。如果在 ch
上於一秒內未收到任何訊息,便選取超時情況,且放棄從 ch 讀取的嘗試。
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
timeout
通道會暫存在一個空間為 1 值的緩衝區中,允許超時 goroutine 傳送至通道,然後結束。goroutine 不知道(或不關心)該值是否已被接收。這表示如果在達到超時時間之前發生 ch
接收,goroutine 將不會永遠擱置。timeout
通道最終會由垃圾收集器取消配置。
(在此範例中,我們使用 time.Sleep
來展示 goroutine 和通道的機制。在實際的程式中,您應該使用 [time.After](/pkg/time/#After)
,此函式會傳回一個通道,並在指定時間後在該通道上傳送。)
讓我們看一下此模式的另一種變化。在此範例中,我們有一個程式同時從多個複製資料庫讀取。程式只需要其中一個答案,而且應該接受最早到達的答案。
函式 Query
會採用一區段資料庫連線及一個 query
字串。它會平行查詢各個資料庫並回傳它收到的第一個回應
func Query(conns []Conn, query string) Result {
ch := make(chan Result)
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query):
default:
}
}(conn)
}
return <-ch
}
在此範例中,封閉會做非阻擋傳送,它是透過使用 select
語句中的 send
操作搭配一個 default
案例來達成。如果傳送無法立刻完成,就會選取預設案例。讓傳送變成非阻擋可以確保迴圈中啟動的所有 goroutine 都不會閒置。不過,如果結果在主函式接收結果前就已到達,傳送可能會失敗,因為沒有人已準備好。
這個問題是教科書上所說的 競爭條件 的範例,不過解決方法很簡單。我們只要確定緩衝頻道 ch
(在呼叫 make 時將緩衝長度新增為第二個引數),即可保證第一個傳送有地方放置值。這會確保傳送必定會成功,並且將會擷取最早到達的第一個值,不論執行順序為何。
這兩個範例說明了 Go 能夠多麼簡潔地表達 goroutine 之間的複雜互動。
下一篇文章: 真正的 Go 專案:SmartTwitter 和 web.go
前一篇文章: 推出 Go Playgrounds
部落格索引