Golang, 以17個簡短代碼片段,切底弄懂 channel 基礎


(原創出處為本博客:http://www.cnblogs.com/linguanh/)

 

前序:

  因為打算自己搞個基於Golang的IM服務器,所以復習了下之前一直沒怎么使用的協程、管道等高並發編程知識。發現自己的channel這塊,也就是管道,實在是有些混亂。然后對着文檔,邊參照官網例子和在編譯器測試,總結了下面這17個例子,設置為簡短的片段,是為了免得混淆太多,阻礙理解。內含注釋豐富,復制粘貼就能編譯使用。

  這里立個 flag,有錯誤歡迎指出,只要你跟着敲完這17個例子,channel的基礎絕對可以掌握!

 

基本概念:

  關於管道 Channel:

    Channels用來同步並發執行的函數並提供它們某種傳值交流的機制。

    Channels的一些特性:通過channel傳遞的元素類型、容器(或緩沖區)和傳遞的方向由“<-”操作符指定。

    c<-123,把值123輸入到管道 c,<-c,把管道 c 的值讀取到左邊,value :=<-c,這樣就是讀到 value里面。

  管道分類:

    無緩沖的與有緩沖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 }

 

歡迎轉載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM