CloudFlare 的 John Graham-Cumming 在 GopherCon 2014 給了一個 A Channel Compendium 的演講,其中介紹了:
- 利用 channel 來通知事件 (Signalling)
- 利用 channel 來隱藏狀態 (Hide state)
- Nil channels 及 closed channels 的特性
- 利用 channel 來實現 timer
整個 talk 的結尾下的非常好:
The Go Way: “small sequential pieces joined by channels”
可以說 Go 的重點特色就是 concurrency programming 的支援,而善用 channel 則是實現 concurrency 的重要能力。
對於剛接觸 go concurrency programming 的開發者,分享一下兩個簡單但好用的 channel 類型: channel of error 及 channel of function。
channel of error
類型爲 chan error 這算是 response channel 的一種,通常用來處理單一非同步操作。應用的場景通常是一個 goroutine 執行工作,另一個 goroutine 等待工作完成後做善後清理的工作。
範例可以看 Go Concurrency Patterns: Pipelines and cancellation 裡的例子。
channel of function
先看一個我覺的寫得太複雜的例子:Programming in Go 書中的 SafeMap 範例,把所有對 map 的操作都透過 channel 送到同一個 goroutine 內來進行,操作結果也是透過 channel 再送回來:
func (sm safeMap) run() {
store := make(map[string]interface{})
for command := range sm {
switch command.action {
case insert:
store[command.key] = command.value
case remove:
delete(store, command.key)
case find:
value, found := store[command.key]
command.result <- findResult{value, found}
case length:
command.result <- len(store)
case update:
value, found := store[command.key]
store[command.key] = command.updater(value, found)
case end:
close(sm)
command.data <- store
}
}
}
(完整的程式請參考 http://play.golang.org/p/I4Rqbguhud)
Find() 的操作如下:
func (sm safeMap) Find(key string) (value interface{}, found bool) {
reply := make(chan interface{})
sm <- commandData{action: find, key: key, result: reply}
result := (<-reply).(findResult)
return result.value, result.found
}
程式的邏輯被分成兩個部份,一部份在 Find() 裡,一部份在 run() 裡,然後兩邊要溝通還要建立相對應的 struct 做爲傳輸用, 爲了共用同一個 commandData 還需要做 type assertion,基本上我不喜歡這樣的寫法。
此時 channel of function 就派上用場了,基本步驟就是將要共享的資料宣告成 chan func() 或其他 func 類型:
type Store map[string]interface{}
type safeMap chan func(store Store)
(完整的程式請參考 http://play.golang.org/p/rDrA6CfCZn)
goroutine 很簡單就是接收 func 然後執行它們:
func (sm safeMap) run() {
store := make(Store)
for command := range sm {
command(store)
}
}
想對於之前的 Find() 更簡潔及易讀:
func (sm safeMap) Find(key string) (value interface{}, found bool) {
done := make(chan struct{})
sm <- func(store Store) {
value, found = store[key]
done <- struct{}{}
}
<-done
return
}