這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
翻譯自 Go Blog。
原文地址:https://blog.golang.org/go-concurrency-patterns-timing-out-and
並發編程有自己的一些習慣用語,逾時就是其中之一。雖然 Golang 的管道並沒有直接支援逾時,但是實現起來並不難。假設遇到了這樣一種情境:在從 管道 ch 中取值之前至少等待 1 秒鐘。我們可以建立一個管道用來傳遞訊號,開啟一個協程休眠一秒鐘,然後給管道傳遞一個值。
timeout := make(chan bool, 1)go func() { time.Sleep(1 * time.Second) timeout <- true}()
然後就可以使用一個 select 語句來從 timeout 或者 ch 管道中擷取資料。如果 ch 管道在 1 秒鐘之後還沒有返回資料,逾時的判斷條件就會觸發 ch 的讀操作將會被拋棄掉。
select { case <-ch: // a read from ch has occurred case <-timeout: // the read from ch has timed out}
timeout 管道的緩衝區空間為 1,因此 timeout 協程將會在發送訊息到管道之後退出執行。協程並不知道(也不關心)管道中的值是否被接受。因此,即使 ch 管道先於 timeout 管道返回了,timeout 協程也不會永久等待。timeout 管道最終會被記憶體回收機制回收掉。
(在上面的樣本中我們使用了 time.Sleep 方法來示範協程和管道的機制,但是在真實的代碼中應該用 time.After 方法,該方法返回了一個管道,並且會在參數指定的時間之後向管道中寫入一個訊息)
下面我們來看這種模式的另外一個變種。我們需要從多個分區資料庫中同時取資料,程式只需要其中最先返回的那個資料。
下面的 Query 方法接受兩個參數:一個資料庫連結的切片和一個資料庫查詢語句。該方法將平行查詢所有資料庫並返回第一個接受到的響應結果。
func Query(conns []Conn, query string) Result { ch := make(chan Result, 1) for _, conn := range conns { go func(c Conn) { select { case ch <- c.DoQuery(query): default: } }(conn) } return <-ch}
在上面這個例子中,go 關鍵字後的閉包實現了一個非阻塞式的查詢請求,因為 DoQuery 方法被放到了帶 default 分支的 select 語句中。假如 DoQuery 方法沒有立即返回,default 分支將會被選中執行。讓查詢請求非阻塞保證了 for 迴圈中建立的協程不會一直阻塞。另外,假如在主方法從 ch 管道中取出值並返回結果之前有第二個查詢結果返回了,管道 ch 的寫操作將會失敗,因為管道並未就緒。
上面描述的問題其實是“競爭關係”的一種典型例子,範例程式碼只是一種很通俗的解決方案。我們只是中管道上設定了緩衝區(通過在管道的 make 方法中傳入第二個參數),保證了第一個寫入者能夠有空間來寫入值。這種策略保證了管道的第一次寫入一定會成功,無論代碼以何種順序執行,第一個寫入的值將會被當作最終的傳回值。
Go 的協程之前可以進行複雜的協作,以上兩個例子就是最簡單的證明。