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
}