- 本文主要講實踐,原理部分會一筆帶過,關於 go 語言併發實現和內存模型後續會有文章。
- channel 實現的源碼不復雜,推薦閱讀,https://github.com/golang/go/...
channel 是用來通訊的
實際上:(數據拷貝了一份,並經過 channel 傳遞,本質就是個隊列)html
須要通訊的地方
例如如下場景:git
記住!channel 不是用來實現鎖機制的,雖然有些地方能夠用它來實現相似讀寫鎖,保護臨界區的功能,但不要這麼用!github
// 利用 time.After 實現 func main() { done := do() select { case <-done: // logic case <-time.After(3 * time.Second): // timeout } } func do() <-chan struct{} { done := make(chan struct{}, 1) go func() { // do something // ... done <- struct{}{} }() return done }
比較常見的一個場景是重試,第一個請求在指定超時時間內沒有返回結果,這時重試第二次,取兩次中最快返回的結果使用。
超時控制在上面有,下面代碼部分就簡單實現調用屢次了。golang
func main() { ret := make(chan string, 3) for i := 0; i < cap(ret); i++ { go call(ret) } fmt.Println(<-ret) } func call(ret chan<- string) { // do something // ... ret <- "result" }
// 最大併發數爲 2 limits := make(chan struct{}, 2) for i := 0; i < 10; i++ { go func() { // 緩衝區滿了就會阻塞在這 limits <- struct{}{} do() <-limits }() }
for ... range c { do }
這種寫法至關於 if _, ok := <-c; ok { do }
併發
func main() { c := make(chan int, 20) go func() { for i := 0; i < 10; i++ { c <- i } close(c) }() // 當 c 被關閉後,取完裏面的元素就會跳出循環 for x := range c { fmt.Println(x) } }
利用 close 廣播app
func main() { c := make(chan struct{}) for i := 0; i < 5; i++ { go do(c) } close(c) } func do(c <-chan struct{}) { // 會阻塞直到收到 close <-c fmt.Println("hello") }
select 自己是阻塞的,當全部分支都不知足就會一直阻塞,若是想不阻塞,那麼一個什麼都不幹的 default 分支是最好的選擇code
select { case <-done: return default: }
儘可能不要用 break label 形式,而是把終止循環的條件放到 for 條件裏來實現htm
for ok { select { case ch <- 0: case <-done: ok = false } }
...隊列
操做 | 值爲 nil 的 channel | 被關閉的 channel | 正常的 channel |
---|---|---|---|
close | panic | panic | 成功關閉 |
c<- | 永遠阻塞 | panic | 阻塞或成功發送 |
<-c | 永遠阻塞 | 永遠不阻塞 | 阻塞或成功接收 |