【轉】Golang 關於通道 Chan 詳解

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文: http://blog.csdn.net/netdxy/article/details/54564436

在用 chan 類型時,發生死結的錯誤,表面上看不出什麼問題

 

 

-------------------------------------------------------------------------------------------------------

首先我們來看線程,在golang裡面也叫goroutine

在讀這篇文章之前,我們需要瞭解一下並發與並行。golang的線程是一種並發機制,而不是並行。它們之間的區別大家可以上網搜一下,網上有很多的介紹。

下面我們先來看一個例子吧

import(         "fmt")funcmain(){    go fmt.Println("1")    fmt.Println("2")    }

 在golang裡面,使用Go這個關鍵字,後面再跟上一個函數就可以建立一個線程。後面的這個函數可以是已經寫好的函數,也可以是一個匿名函數

funcmain(){    var i=3    go func(a int) {        fmt.Println(a)        fmt.Println("1")    }(i)    fmt.Println("2")}

上面的代碼就建立了一個匿名函數,並且還傳入了一個參數i,下面括弧裡的i是實參,a是形參。

那麼上面的代碼能按照我們預想的列印1、2、3嗎?告訴你們吧,不能,程式只能列印出2。下面我把正確的代碼貼出來吧

import(    "fmt"    "time"    )funcmain(){    var i = 3    go func(a int) {        fmt.Println(a)        fmt.Println("1")    }(i)    fmt.Println("2")    time.Sleep(1 * time.Second)}

我只是在最後加了一行讓主線程休眠一秒的代碼,程式就會依次列印出2、3、1。 

那為什麼會這樣呢?因為程式會優先執行主線程,主線程執行完成後,程式會立即退出,沒有多餘的時間去執行子線程。如果在程式的最後讓主線程休眠1秒鐘,那程式就會有足夠的時間去執行子線程。

線程先講到這裡,下面我們來看看通道吧。

通道又叫channel,顧名思義,channel的作用就是在多線程之間傳遞資料的。

建立無緩衝channel

chreadandwrite :=make(chan int)

chonlyread := make(<-chan int) //建立唯讀channel 
chonlywrite := make(chan<- int) //建立唯寫channel 
下面我們來看一個例子:

ch :=make(chan int)         ch <- 1      go func() {        <-ch        fmt.Println("1")      }()      fmt.Println("2")  

這段代碼執行時會出現一個錯誤:fatal error: all goroutines are asleep - deadlock!

這個錯誤的意思是說線程陷入了死結,程式無法繼續往下執行。那麼造成這種錯誤的原因是什麼呢?

我們建立了一個無緩衝的channel,然後給這個channel賦值了,程式就是在賦值完成後陷入了死結。因為我們的channel是無緩衝的,即同步的,賦值完成後來不及讀取channel,程式就已經阻塞了。這裡介紹一個非常重要的概念:channel的機制是先進先出,如果你給channel賦值了,那麼必須要讀取它的值,不然就會造成阻塞,當然這個只對無緩衝的channel有效。對於有緩衝的channel,發送方會一直阻塞直到資料被拷貝到緩衝區;如果緩衝區已滿,則發送方只能在接收方取走資料後才能從阻塞狀態恢複。

對於上面的例子有兩種解決方案:

1、給channel增加緩衝區,然後在程式的最後讓主線程休眠一秒,代碼如下:

ch :=make(chan int,1)    ch <- 1    go func() {        v := <-ch        fmt.Println(v)    }()    time.Sleep(1 * time.Second)    fmt.Println("2")

 這樣的話程式就會依次列印出1、2

2、把ch<-1這一行代碼放到子線程代碼的後面,代碼如下:

ch :=make(chan int)    go func() {        v := <-ch        fmt.Println(v)    }()    ch <- 1    fmt.Println("2")

  

這裡就不用讓主線程休眠了,因為channel在主線程中被賦值後,主線程就會阻塞,直到channel的值在子線程中被取出。

最後我們看一個生產者和消費者的例子:

import (    "fmt"    "time")func produce(p chan<- int) {    for i := 0; i < 10; i++ {        p <- i        fmt.Println("send:", i)    }}func consumer(c <-chan int) {    for i := 0; i < 10; i++ {        v := <-c        fmt.Println("receive:", v)    }}func main() {    ch := make(chan int)    go produce(ch)    go consumer(ch)    time.Sleep(1 * time.Second)}

  

在這段代碼中,因為channel是沒有緩衝的,所以當生產者給channel賦值後,生產者這個線程會阻塞,直到消費者線程將channel中的資料取出。消費者第一次將資料取出後,進行下一次迴圈時,消費者的線程也會阻塞,因為生產者還沒有將資料存入,這時程式會去執行生產者的線程。程式就這樣在消費者和生產者兩個線程間不斷切換,直到迴圈結束。

下面我們再看一個帶緩衝的例子:

import (    "fmt"    "time")func produce(p chan<- int) {    for i := 0; i < 10; i++ {        p <- i        fmt.Println("send:", i)    }}func consumer(c <-chan int) {    for i := 0; i < 10; i++ {        v := <-c        fmt.Println("receive:", v)    }}func main() {    ch := make(chan int, 10)    go produce(ch)    go consumer(ch)    time.Sleep(1 * time.Second)}

在這個程式中,緩衝區可以儲存10個int類型的整數,在執行生產者線程的時候,線程就不會阻塞,一次性將10個整數存入channel,在讀取的時候,也是一次性讀取。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.