(原創出處爲本博客:http://www.cnblogs.com/linguanh/)編程
前序:緩存
由於打算本身搞個基於Golang的IM服務器,因此複習了下以前一直沒怎麼使用的協程、管道等高併發編程知識。發現本身的channel這塊,也就是管道,實在是有些混亂。而後對着文檔,邊參照官網例子和在編譯器測試,總結了下面這17個例子,設置爲簡短的片斷,是爲了省得混淆太多,阻礙理解。內含註釋豐富,複製粘貼就能編譯使用。服務器
這裏立個 flag,有錯誤歡迎指出,只要你跟着敲完這17個例子,channel的基礎絕對能夠掌握!併發
基本概念:函數
關於管道 Channel:高併發
Channels用來同步併發執行的函數並提供它們某種傳值交流的機制。測試
Channels的一些特性:經過channel傳遞的元素類型、容器(或緩衝區)和傳遞的方向由「<-」操做符指定。spa
c<-123,把值123輸入到管道 c,<-c,把管道 c 的值讀取到左邊,value :=<-c,這樣就是讀到 value裏面。code
管道分類:協程
無緩衝的與有緩衝channel有着重大差異,那就是一個是同步的 一個是非同步的。
好比
c1:=make(chan int) 無緩衝
c2:=make(chan int,1) 有緩衝
例如:c1<-1
無緩衝: 不單單是向 c1 通道放 1,而是一直要等有別的攜程 <-c1 接手了這個參數,那麼c1<-1纔會繼續下去,要否則就一直阻塞着。
有緩衝: c2<-1 則不會阻塞,由於緩衝大小是1(實際上是緩衝大小爲0),只有當放第二個值的時候,第一個還沒被人拿走,這時候纔會阻塞。
例子s
演示 無緩存 和 有緩衝 的 channel 的樣子
1 func test0(){ 2 /** 演示 無緩存 和 有緩衝 的 channel 的樣子 */ 3 done := make(chan bool) /** 無緩衝 */ 4 done1 := make(chan bool,1) /** 有緩衝 */ 5 println(done,done1) 6 }
演示 無緩衝在同一個main裏面的 死鎖例子
1 func test1() { 2 /** 編譯錯誤 deadlock,阻死 main 進程 */ 3 /** 演示 無緩衝在同一個main裏面的 死鎖例子 */ 4 done := make(chan bool) 5 done<-true /** 這句是輸入值,它會一直阻塞,等待讀取 */ 6 <-done /** 這句是讀取,可是在上面已經阻死了,永遠走不到這裏 */ 7 println("完成") 8 }
演示僅有 輸入 語句,但沒 讀取語句 的死鎖例子
1 func test2() { 2 /** 編譯錯誤 deadlock,阻死 main 進程 */ 3 /** 演示僅有 輸入 語句,但沒 讀取語句 的死鎖例子 */ 4 done := make(chan bool) 5 done<-true /** 輸入,一直等待讀取,哪怕沒讀取語句 */ 6 println("完成") 7 }
演示僅有 讀取 語句,但沒 輸入語句 的死鎖例子
1 func test3() { 2 /** 編譯錯誤 deadlock,阻死 main 進程 */ 3 /** 演示僅有 讀取 語句,但沒 輸入語句 的死鎖例子 */ 4 done := make(chan bool) 5 <-done /** 讀取輸出,前面沒有輸入語句,done 是 empty 的,因此一直等待輸入 */ 6 7 println("完成") 8 }
演示,協程的阻死,不會影響 main
1 func test4() { 2 /** 編譯經過 */ 3 /** 演示,協程的阻死,不會影響 main */ 4 done := make(chan bool) 5 go func() { 6 <-done /** 一直等待 */ 7 }() 8 println("完成") 9 /** 10 * 控制檯輸出: 11 * 完成 12 */ 13 }
在 test4 的基礎上,無緩衝channel在協程 go routine 裏面阻塞死
1 func test5() { 2 /** 編譯經過 */ 3 /** 在 test4 的基礎上,無緩衝channel在協程 go routine 裏面阻塞死 */ 4 done := make(chan bool) 5 go func() { 6 println("我可能會輸出哦") /** 阻塞前的語句 */ 7 done<-true /** 這裏阻塞死,可是上面那句有可能輸出,見 test3 的結論 */ 8 println("我永遠不會輸出") 9 <-done /** 這句也不會走到,除非在別的協程裏面讀取,或者在 main */ 10 }() 11 println("完成") 12 }
編譯經過,在 test5 的基礎上演示,延時 main 的跑完
1 func test6() { 2 /** 編譯經過,在 test5 的基礎上演示,延時 main 的跑完 */ 3 done := make(chan bool) 4 go func() { 5 println("我可能會輸出哦") 6 done<-true /** 這裏阻塞死 */ 7 println("我永遠不會輸出") 8 <-done /** 這句也不會走到 */ 9 }() 10 time.Sleep(time.Second * 1) /** 加入延時 1 秒 */ 11 println("完成") 12 /** 13 * 控制檯輸出: 14 * 我可能會輸出哦 15 * 完成 16 */ 17 /** 18 * 結論: 19 * 若是在 go routine 中阻塞死,也可能不會把阻塞語句前的內容輸出, 20 * 由於main已經跑完了,因此延時一會,等待 go routine 21 */ 22 }
演示無緩衝channel 在 不一樣的位置裏面接收填充和接收
1 func test7() { 2 /** 編譯經過,演示無緩衝channel 在 不一樣的位置裏面接收填充和接收*/ 3 done := make(chan bool) 4 go func() { 5 done<-true /** 直到,<-done 執行,不然這裏阻塞死 */ 6 println("我永遠不會輸出,除非 <-done 執行") 7 8 }() 9 <-done /** 這裏接收,在輸出完成以前,那麼上面的語句將會走通 */ 10 println("完成") 11 /** 12 * 控制檯輸出: 13 * 我永遠不會輸出,除非 <-done 執行 14 * 完成 15 */ 16 }
演示無緩衝channel 在不一樣地方接收的影響
1 func test8() { 2 /** 編譯經過,演示無緩衝channel 在不一樣地方接收的影響 */ 3 done := make(chan bool) 4 go func() { 5 done<-true /** 直到,<-done 執行,不然這裏阻塞死 */ 6 println("我永遠不會輸出,除非 <-done 執行") 7 }() 8 println("完成") 9 <-done /** 這裏接收,在輸出完成以後 */ 10 /** 11 * 控制檯輸出: 12 * 完成 13 * 我永遠不會輸出,除非 <-done 執行 14 */ 15 }
沒緩存的 channel 使用 close 後,不會阻塞
1 func test9() { 2 /** 編譯經過 */ 3 /** 演示,沒緩存的 channel 使用 close 後,不會阻塞 */ 4 done := make(chan bool) 5 close(done) 6 //done<-true /** 關閉了的,不能再往裏面輸入值 */ 7 <-done /** 這句是讀取,可是在上面已經關閉 channel 了,不會阻死 */ 8 println("完成") 9 }
沒緩存的 channel,在 go routine 裏面使用 close 後,不會阻塞
1 func test10() { 2 /** 編譯經過 */ 3 /** 演示,沒緩存的 channel,在 go routine 裏面使用 close 後,不會阻塞 */ 4 done := make(chan bool) 5 go func() { 6 close(done) 7 }() 8 //done<-true /** 關閉了的,不能再往裏面輸入值 */ 9 <-done /** 這句是讀取,可是在上面已經關閉 channel 了,不會阻死 */ 10 println("完成") 11 }
有緩衝的 channel 不會阻塞的例子
1 func test11() { 2 /** 編譯經過 */ 3 /** 有緩衝的 channel 不會阻塞的例子 */ 4 done := make(chan bool,1) 5 done<-true 6 <-done 7 println("完成") 8 }
有緩衝的 channel 會阻塞的例子
1 func test12() { 2 /** 編譯經過 */ 3 /** 有緩衝的 channel 會阻塞的例子 */ 4 done := make(chan bool,1) 5 // done<-true /** 註釋這句 */ 6 <-done /** 雖然是有緩衝的,可是在沒輸入的狀況下,讀取,會阻塞 */ 7 println("完成") 8 }
有緩衝的 channel 會阻塞的例子
1 func test13() { 2 /** 編譯不經過 */ 3 /** 有緩衝的 channel 會阻塞的例子 */ 4 done := make(chan bool,1) 5 done<-true 6 done<-false /** 放第二個值的時候,第一個還沒被人拿走,這時候纔會阻塞,根據緩衝值而定 */ 7 println("完成") 8 }
有緩衝的 channel 不會阻塞的例子
1 func test14() { 2 /** 編譯經過 */ 3 /** 有緩衝的 channel 不會阻塞的例子 */ 4 done := make(chan bool,1) 5 done<-true /** 不會阻塞在這裏,等待讀取 */ 6 7 println("完成") 8 }
有緩衝的channel,若是在 go routine 中使用,必定要作適當的延時,不然會輸出來不及,由於main已經跑完了,因此延時一會,等待 go routine
1 func test15() { 2 /** 編譯經過 */ 3 /** 有緩衝的channel 在 go routine 裏面的例子 */ 4 done := make(chan bool,1) 5 go func() { 6 /** 不會阻塞 */ 7 println("我可能會輸出哦") 8 done<-true /** 若是把這個註釋,也會致使 <-done 阻塞 */ 9 println("我也可能會輸出哦") 10 <-done 11 println("別註釋 done<-true 哦,否則我就輸出不了了") 12 }() 13 time.Sleep(time.Second * 1) /** 1秒延時,去掉就可能上面的都不會輸出也有能夠輸出,routine 調度 */ 14 println("完成") 15 /** 16 * 控制檯輸出: 17 * 我可能會輸出哦 18 * 我也可能會輸出哦 19 * 完成 20 */ 21 /** 22 * 結論: 23 * 有緩衝的channel,若是在 go routine 中使用,必定要作適當的延時,不然會輸出來不及, 24 * 由於main已經跑完了,因此延時一會,等待 go routine 25 */ 26 }
多channel模式
1 func getMessagesChannel(msg string, delay time.Duration) <-chan string { 2 c := make(chan string) 3 go func() { 4 for i := 1; i <= 3; i++ { 5 c <- fmt.Sprintf("%s %d", msg, i) 6 time.Sleep(time.Millisecond * delay) /** 僅僅起到,下一次的 c 在什麼時候輸入 */ 7 } 8 }() 9 return c 10 } 11 12 func test16() { 13 /** 編譯經過 */ 14 /** 複雜的演示例子 */ 15 /** 多channel模式 */ 16 c1 := getMessagesChannel("第一", 600 ) 17 c2 := getMessagesChannel("第二", 500 ) 18 c3 := getMessagesChannel("第三", 5000) 19 20 /** 層層限制阻塞 */ 21 /** 這個 for 裏面會形成等待輸入,c1 會阻塞 c2 ,c2 阻塞 c3 */ 22 /** 因此它老是,先輸出 c1 而後是 c2 最後是 c3 */ 23 for i := 1; i <= 3; i++ { 24 /** 每次循環提取一輪,共三輪 */ 25 println(<-c1) /** 除非 c1 有輸入值,不然就阻塞下面的 c2,c3 */ 26 println(<-c2) /** 除非 c2 有輸入值,不然就阻塞下面的 c3 */ 27 println(<-c3) /** 除非 c3 有輸入值,不然就阻塞進入下一輪循環,反覆如此 */ 28 } 29 /** 30 * 這個程序的運行結果,首輪的,第一,第二,第三 很快輸出,由於 31 * getMessagesChannel 函數的延時 在 輸入值以後,在第二輪及其以後 32 * 由於下一個 c3 要等到 5秒後才能輸入,因此會阻塞第二輪循環的開始5秒,如此反覆。 33 */ 34 /** 修改:若是把 getMessagesChannel 裏面的延時,放在輸入值以前,那麼 c3 老是等待 5秒 後輸出 */ 35 }
在 test15 基礎修改的,複雜演示例,多channel 的選擇,延時在輸入以後的狀況
1 func test17() { 2 /** 編譯經過 */ 3 /** 在 test15 基礎修改的,複雜演示例子 */ 4 /** 多channel 的選擇,延時在輸入以後的狀況 */ 5 c1 := getMessagesChannel("第一", 600 ) 6 c2 := getMessagesChannel("第二", 500 ) 7 c3 := getMessagesChannel("第三", 5000) 8 /** 3x3 次循環,是 9 */ 9 /** select 老是會把最早完成輸入的channel輸出,並且,互不限制 */ 10 /** c1,c2,c3 每兩個互不限制 */ 11 for i := 1; i <= 9; i++ { 12 select { 13 case msg := <-c1: 14 println(msg) 15 case msg := <-c2: 16 println(msg) 17 case msg := <-c3: 18 println(msg) 19 } 20 } 21 /** 22 * 這個程序的運行結果: 23 * 第二 1,第三 1,第一 1,第二 2,第一 2,第二 3,第一 3,第三 2,第三 3 24 */ 25 /** 分析:前3次輸出,「第一」,「第二」,「第三」,都有,並且 26 * 是隨機順序輸出,由於協程的調度,第4,5,6次,因爲「第二」只延時 500ms, 27 * 比 600ms 和 5000ms 都要小,那麼它先輸出,而後是「第一」,此時「第三」還不能輸出, 28 * 由於它還在等5秒。此時已經輸出5次,再過 500ms,"第三"的5秒還沒走完,因此繼續輸出"第一", 29 * 再過 100ms,500+100=600,"第二"也再完成了一次,那麼輸出。至此,"第一"和"第二"已經 30 * 把管道的 3 個值所有輸出,9-7 = 2,剩下兩個是 "第三"。此時,距離首次的 5000ms 完成, 31 * 還有,500-600-600 = 3800ms,達到後,"第三" 將輸出,再過5秒,最後一次"第三輸出" 32 */ 33 }
歡迎轉載