Golang goroutine與channel

來源:互聯網
上載者:User

  使用goroutine的方法很簡單,直接在語句前面加go關鍵字即可,如果是多核處理器的電腦,使用gorountine,就會在另外一個CPU上執行goroutine,子協程不一定會和主協程在一個CPU上執行。

  這裡有兩個注意的地方,使用go關鍵字的進程稱之為子協程,而沒有使用go關鍵字的進程稱之為主協程,在多CPU的機器上,如果有多個協程,那麼這些協程的執行順序以及執行完成的順序都是不確定的,但有一點,如果主協程結束,那麼整個進程就結束了,不論子協程是否結束,整個進程都結束了,也就看不到子協程的運行結果了。

樣本1

package mainimport "fmt"func main() {go fmt.Println("hello")  //子協程fmt.Println("world")   //主協程}

  上面的代碼執行之後,會有三種結果:

  1、hello world      2、world       3、world hello

  上面這三個結果都是正確的。

  對於第一種,子協程在一個CPU上面執行輸出hello之後,再輪到主協程中執行輸出world。

  對於第二種,子協程在一個CPU上還沒來得及執行,或者說沒有執行完成,但是主協程已經執行完成了,此時整個進程就運行完畢了,所以就看不到子協程的運行結果了。

  對於第三種,主協程中語句還沒執行完成的時候,子協程已經運行完畢,幾乎同時完成,所以會出現著這種情況。

  可以參考下面這個圖來理解:

      

 

建立channel

  channel的零值是nil

chi := make(chan int)chs := make(chan string)chif := make(chan interface{}) //因為可以認為所有的類型都實現了空介面//所以這個chif的chan接收任何類型的chan

  

發送和接收channel

chi := make(chan int)chi <- 3//發送i := <-chi//接收,儲存值<-chi//接收,不儲存值

  

關閉channel

chi := make(chan int)chi <- 3   //發送i := <-chi //接收,儲存值<-chi      //接收,不儲存值close(chi) //關閉channel

  對一個已經關閉的channel發送資料,都會導致panic異常。對於一個已經關閉的channel,可以繼續接收這個已關閉的channel中的資料,如果沒有資料的話,接收到的就是chan類型的零值。

 

無緩衝channel(同步channel)

  一個基於無緩衝的channel:

  1、發送操作將導致寄件者的gorountine阻塞,直到有兩外一個goroutine在相同的channel上執行接收操作之後,寄件者的阻塞才會解開。

  2、同理,如果接收操作先發生,那麼接受者goroutine也將阻塞,直到另一個goroutine在相同的channel上執行發送操作,接受者的阻塞才會解開。

  基於無緩衝的channel的發送和接收操作將導致兩個goroutine做一次同步操作,所以也叫作同步channel。

  向一個無緩衝的channel中發送多次資料,即多個goroutine都向同一個goroutine中發送資料,並且只有一個協程來接收資料,這個時候會後什麼問題嗎?會發生覆蓋?或者是只有第一個發送的值才會保留,後面發送的資料都無效?和有緩衝channel有什麼區別?

  請看下面的樣本:

package mainimport "fmt"func main() {chi := make(chan int) //無緩衝go func() {chi <- 10chi <- 30chi <- 40}()//fmt.Println(<-chi)//10//fmt.Println(<-chi, <-chi) //10 30//fmt.Println(<-chi, <-chi, <-chi)//10 30 40fmt.Println(<-chi, <-chi, <-chi, <-chi) //deadlock}

  從上面的例子中可以看到,有一個gorountine向一個無緩衝的channel中發送三個資料,此時,分這幾種情況:

  0、chi還沒有來得及向channel中發送資料時,主協程阻塞,子協程可以向其中發送一個資料。

  1、 主協程只從channel中接收一次資料:子協程向chi中發送了一個資料,此時,自協程阻塞,等待chi中的資料被消費之後,才繼續運行。 那麼,主協程接收的第一個資料就是第一次向這個channel中發送的資料10,之後,channel空閑,寄件者可以發送第二個資料30,子協程再次阻塞,等待主協程接收資料,但是此時主協程不接受資料,主協程已經執行完成了,那麼整個進程就完成了,所以最終只輸出10這一個資料。此時子協程也會跟著被結束。

  2、 主協程只從channel中接收兩次資料:接著上一步,此時主協程第二次接收資料的話,接收到的是30;之後,chi空閑,子協程再次向chi中發送資料40,然後又阻塞,但是此時主協程已經完成,所以子協程再次被結束。

  3、主協程從channel中接收三次資料:接著上一步,此時主協程接收到的資料應該是40,之後,chi空閑,子協程也不會像其中發送資料了,同時,子協程也已經運行結束了,所以,主協程中輸出10 30 40。

  4、主協程從channel中接收四次資料:接著上一步,因為chi已經空閑,且子協程已經結束,表示沒有協程向chi中發送資料了,但是chi並沒有關閉,那麼主協程中第四次的接收資料就會一直等待,最終造成死結。解決這個問題,可以在子協程中發送第三次資料40之後,執行close(chi),之後,主協程第四次接收資料便不會引起死結,接收到的資料時0(chi的類型零值)

   

使用range遍曆channel

package mainimport ("fmt")func main() {num := make(chan int)go func() {num <- 30num <- 40close(num)}()for x := range num {fmt.Println(x)}//輸出//30//40}

  使用range遍曆channel時,從channel中接收資料,如果從channel中擷取到了資料,那麼返回的結果就是擷取到的值;如果channel已經關閉,那麼對於無緩衝的channel而言,就沒有資料可擷取了,但是range的結果是channel的類型零值。

串聯channel

  下面是一個串聯的channel,實現

package mainimport ("fmt""time")func main() {num := make(chan int)sqrt := make(chan int)go func() {for x := 0; ; x++ {num <- xtime.Sleep(time.Second)}}()go func() {for {x := <-numsqrt <- x * x}}()for {fmt.Println(<-sqrt)}}

  上面這個代碼中,存在問題:如果第一個子協程不再往num中發送資料,那麼,第二個子協程就會阻塞,同理,主協程sqrt取資料也會阻塞,最終引發死結。

  解決方案:

package mainimport ("fmt""time")func main() {num := make(chan int)sqrt := make(chan int)go func() {for x := 0; x < 10; x++ {num <- xtime.Sleep(time.Second)}//不再向num中發送資料時,關閉channel,防止死結close(num)}()go func() {for x := range num {sqrt <- x * x}//num已經關閉了,所以也要關閉sqrtclose(sqrt)}()for x := range sqrt {fmt.Println(x)}}

  

 單向channel

  使用channel作為參數進行通訊時,在函數內部,有的channel只接受資料,有的channel只發送資料,這是可以使用單向的channel表示。

  雙向的channel:因為關閉channel操作 一般是 發送方通知接收方,發送方不再向channel中發送新的資料了,所以通常只有在寄件者所在的goroutine才會調用close函數,在接收channel資料的goroutine中關閉channel顯然是不合理的,很有可能會引起錯誤,因為發送資料的goroutine可能不知道channel已經關閉了,所以他一旦向channle中發送資料,就會引發錯誤。

  單向channel:只有寄件者的goroutine中可以調用close函數來關閉單項channel。如果一個goroutine中,對一個只接受的channel調用close函數,或引發編譯錯誤。

  任何雙向的channel向單向的channel賦值都將導致隱式轉換,但是不能反向轉換。

  聲明單向channle  

chi := make(<-chan int) //只能被讀資料的channelchi := make(chan <- int) //只接收資料的channel

  

有緩衝的channel

chi := make(chan int)//無緩衝chii := make(chan int,10)//有緩衝channel,緩衝空間可存10個值

  定義了一個有緩衝的channel之後:

  在往channel中發資料的時候,只有當channel的緩衝區數量不為0時,資料才能寫入,寫資料的goroutine才不會阻塞。

  在從channel中讀資料的時候,只有當channel不為空白的時候,讀channel的goroutine才不會阻塞。

package mainimport ("fmt""time")func WriteData(chi chan int) {go func() {for x := 0; ; x++ {chi <- xtime.Sleep(2 * time.Second)}}()go func() {for x := 0; ; x++ {chi <- xtime.Sleep(time.Second)}}()go func() {for x := 0; ; x++ {chi <- xtime.Sleep(2 * time.Second)}}()}func main() {chi := make(chan int, 3)WriteData(chi)for x := range chi {fmt.Println(x)}}

  此例中,如果寫資料時channel已滿,那麼寫資料的goroutine會阻塞。如果讀channel時,channel為空白,那麼讀資料的goroutine會阻塞。

 

並發中的迴圈

package mainimport "fmt"func loop() {for x := 0; x < 10; x++ {go func() {fmt.Println(x)}()}}func main() {loop()}

  上面這個代碼執行的時候,可能有以下問題:

  1、沒有輸出   2、只輸出一部分值   3、輸出很多相同的值   4、輸出的值全相同

  對於1、2、3的問題,是因為主協程執行速度太快,各個goroutine還沒來得及執行,主協程就結束了,於是整個進程就結束了,也就看不到輸出了。

  解決方案:在主協程中加一個休眠,給各個協程時間去執行。

package mainimport ("fmt""time")func loop() {for x := 0; x < 10; x++ {go func() {fmt.Println(x)}()}}func main() {loop()time.Sleep(time.Second)}

  對於第四個問題,是因為loop中的迴圈太快,而各個goroutine太慢,於是所有goroutine在執行前,迴圈已經完畢,此時i為10,於是全部都列印10了。

  解決方案:將i作為參數(閉包)

package mainimport ("fmt""time")func loop() {for x := 0; x < 10; x++ {go func(i int) {//i為形參fmt.Println(i)}(x) //x為實參}}func main() {loop()time.Sleep(time.Second)}

  

 select接收channel

package mainimport ("fmt")func loop(ch chan int, chi chan int) {go func() {for {ch <- 9}}()go func() {for {chi <- 8}}()}func main() {ch, chi := make(chan int), make(chan int)loop(ch, chi)select {case <-ch:fmt.Println("接收到ch中的資料")case <-chi:fmt.Println("接收到chi中的資料")}}

  select類似於if  else,每一個case表示接收到channel的資料,執行對應的操作。只不過上面這個例子中,只會接收一次,要麼接收的是ch,要麼是chi,之後就不會再從channel中讀資料了。

  如果要一直等待接收多個channel,即,要接收多個channel中的資料,可以使用for + select即可:

package mainimport ("fmt""time")func loop(ch chan int, chi chan int) {go func() {for {ch <- 9time.Sleep(2 * time.Second)}}()go func() {for {chi <- 8time.Sleep(3 * time.Second)}}()}func main() {ch, chi := make(chan int), make(chan int)loop(ch, chi)for {select {case <-ch:fmt.Println("接收到ch中的資料")case <-chi:fmt.Println("接收到chi中的資料")}}}

  

並發的退出 

package mainimport ("fmt""time")func main() {exit := make(chan bool)for x := 1; x < 5; x++ {go func(i int) {for {select {case <-exit:fmt.Println("第", i, "個goroutine結束運行")returndefault:fmt.Println("第", i, "個goroutine正在運行")}time.Sleep(time.Second)}}(x)}time.Sleep(time.Second * 5)  //各個goroutine運行close(exit)  //廣播結束訊息time.Sleep(time.Second * 1)  //等待各個goroutine列印結束資訊}

  

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.