go channel

知識點:
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

  • 向已關閉的channel發送數據,則會引起pannic;
  • channel關閉以後,仍然能夠從channel中讀取剩餘的數據,直到數據所有讀取完成。
  • 關閉已經關閉的channel會致使panic
  • channel若是未關閉,在讀取超時會則會引起deadlock異常

clipboard.png

循環管道注意:code

  • 使用range循環管道,若是管道未關閉會引起deadlock錯誤。

image.png

  • 若是採用for死循環已經關閉的管道,當管道沒有數據時候,讀取的數據會是管道的默認值,而且循環不會退出。

clipboard1.png

問題來了,如何知道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:隊列

  • select語句中除default外,每一個case操做一個channel,要麼讀要麼寫
  • select語句中除default外,各case執行順序是隨機的
  • 若是select全部case中的channel都未ready,則執行default中的語句而後退出select流程
  • select語句中若是沒有default語句,則會阻塞等待任一case
  • select語句中讀操做要判斷是否成功讀取,關閉的channel也能夠讀取

舉例:
(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語句具是隨機的。

相關文章
相關標籤/搜索