知識點:
1.channel的定義和聲明
2.帶緩衝區/不帶緩衝區 的channel
3.如何優雅的關閉channel
4.chan的死鎖機制
5.channel應用場景
6.select 應用數據結構
channel的定義:併發
channel是Go語言中各個併發結構體(goroutine)以前的通訊機制。 通俗的講,就是各個goroutine之間通訊的」管道「,有點相似於Linux中的管道。
1.聲明channel 2.引用類型 3.單向channel var 變量名 chan 數據類型 channel和和map相似,channel也一個對應make建立的底層數據結構的引用。 當咱們複製一個channel或用於函數參數傳遞時,咱們只是拷貝了一個channel引用,所以調用者和被調用者將引用同一個channel對象。和其它的引用類型同樣,channel的零值也是nil。定義一個channel時,也須要定義發送到channel的值的類型。 // 方法一:channel的建立賦值 var ch chan int; ch = make(chan int); // 方法二:短寫法 ch:=make(chan int); // 方法三:綜合寫法:全局寫法!!!! var ch = make(chan int); 單向chan //定義只讀的channel read_only := make (<-chan int) //定義只寫的channel write_only := make (chan<- int)
帶緩衝區/不帶緩衝區 的channel異步
帶緩衝區channel:定義聲明時候制定了緩衝區大小(長度),能夠保存多個數據。
ch := make(chan int ,10) //帶緩衝區 (只有當隊列塞滿時發送者會阻塞,隊列清空時接受着會阻塞。)
不帶緩衝區channel:只能存一個數據,而且只有當該數據被取出時候才能存下一個數據。
ch := make(chan int) //不帶緩衝區
無緩衝channel詳細解釋: 1.一次只能傳輸一個數據 2.同一時刻,同時有 讀、寫兩端把持 channel,同步通訊。 若是隻有讀端,沒有寫端,那麼 「讀端」阻塞。 若是隻有寫端,沒有讀端,那麼 「寫端」阻塞。 讀channel: <- channel 寫channel: channel <- 數據 舉一個形象的例子: 同步通訊: 數據發送端,和數據接收端,必須同時在線。 —— 無緩衝channel 打電話。打電話只有等對方接收纔會通,要否則只能阻塞
帶緩channel詳細解釋: 舉一個形象的例子: 異步通訊:數據發送端,發送完數據,當即返回。數據接收端有可能當即讀取,也可能延遲處理。 —— 有緩衝channel 不用等對方接受,只需發送過去就行。 發信息。短信。發送完就好,管他何時讀信息。
如何優雅的關閉channel函數
注意:
讀寫操做注意:spa
循環管道注意:code
問題來了,如何知道channel是否關閉,如何優雅的關閉channel,
協程
一個適用的原則是不要從接收端關閉channel,也不要關閉有多個併發發送者的channel。對象
讀取channel的方式有兩種: close(ch) 一種方式: value, ok := <- ch ok是false,就表示已經關閉。 另外一種方式,就是上面例子中使用的方式: for value := range ch { } channel關閉以後,仍然能夠從channel中讀取剩餘的數據, 直到數據所有讀取完成,會跳出循環
select專題:blog
select是Golang在語言層面提供的多路IO複用的機制, 其能夠檢測多個channel是否ready(便是否可讀或可寫)
總結select:隊列
舉例:
(1)題目一:下面的程序輸出是什麼?
package main import ( "fmt" "time" ) func main() { chan1 := make(chan int) chan2 := make(chan int) go func() { chan1 <- 1 time.Sleep(5 * time.Second) }() go func() { chan2 <- 1 time.Sleep(5 * time.Second) }() select { case <-chan1: fmt.Println("chan1 ready.") case <-chan2: fmt.Println("chan2 ready.") default: fmt.Println("default") } fmt.Println("main exit.") }
程序中聲明兩個channel,分別爲chan1和chan2,依次啓動兩個協程,分別向兩個channel中寫入一個數據就進入睡眠。select語句兩個case分別檢測chan1和chan2是否可讀,若是都不可讀則執行default語句。
參考答案:
select中各個case執行順序是隨機的,若是某個case中的channel已經ready,則執行相應的語句並退出select流程,若是全部case中的channel都未ready,則執行default中的語句而後退出select流程
。另外,因爲啓動的協程和select語句並不能保證執行順序,因此也有可能select執行時協程還未向channel中寫入數據,因此select直接執行default語句並退出。因此,如下三種輸出都有可能:
可能的輸出一:
chan1 ready. main exit.
可能的輸出二:
chan2 ready. main exit.
可能的輸出三:
default main exit.
(2)題目二:下面的程序執行到select時會發生什麼?
package main import ( "fmt" "time" ) func main() { chan1 := make(chan int) chan2 := make(chan int) writeFlag := false go func() { for { if writeFlag { chan1 <- 1 } time.Sleep(time.Second) } }() go func() { for { if writeFlag { chan2 <- 1 } time.Sleep(time.Second) } }() select { case <-chan1: fmt.Println("chan1 ready.") case <-chan2: fmt.Println("chan2 ready.") } fmt.Println("main exit.") }
程序中聲明兩個channel,分別爲chan1和chan2,依次啓動兩個協程,協程會判斷一個bool類型的變量writeFlag來決定是否要向channel中寫入數據,因爲writeFlag永遠爲false,因此實際上協程什麼也沒作。select語句兩個case分別檢測chan1和chan2是否可讀,這個select語句不包含default語句。
參考答案:select會按照隨機的順序檢測各case語句中channel是否ready,若是某個case中的channel已經ready則執行相應的case語句而後退出select流程,若是全部的channel都未ready且沒有default的話,則會阻塞等待各個channel。因此上述程序會一直阻塞。
(3)題目三:下面程序有什麼問題?
package main import ( "fmt" ) func main() { chan1 := make(chan int) chan2 := make(chan int) go func() { close(chan1) }() go func() { close(chan2) }() select { case <-chan1: fmt.Println("chan1 ready.") case <-chan2: fmt.Println("chan2 ready.") } fmt.Println("main exit.") }
程序中聲明兩個channel,分別爲chan1和chan2,依次啓動兩個協程,協程分別關閉兩個channel。select語句兩個case分別檢測chan1和chan2是否可讀,這個select語句不包含default語句。
參考答案:select會按照隨機的順序檢測各case語句中channel是否ready,考慮到已關閉的channel也是可讀的,因此上述程序中select不會阻塞,具體執行哪一個case語句具是隨機的。