golang筆記——併發

  go語言中的main函數也是運行在一個單獨的goroutine中的,通常稱爲 main goroutine,main函數結束時,會打斷其它 goroutine 的執行,可是其它 goroutine 不會打斷其它的 goroutine 的執行,除非是經過通訊讓對方自行停止。緩存

  先來看一個最簡單的併發例子,一個時間服務器:(偏題了,不該該使用這個例子,應該突出重點,這個例子能夠放到tcp那節)服務器

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }   
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Print(err)
            continue
        }   
        go handleConn(conn)
    }   
}

func handleConn(c net.Conn) {
    defer c.Close()
    for {
        _, err := io.WriteString(c, time.Now().Format("2006/01/02 15:04:05\n"))
        if err != nil {
            return
        }   
        time.Sleep(1 * time.Second)
    }   
}

  從這個例子能夠看出,使用go開啓一個tcp服務很是簡潔,此外從該例子中,咱們能夠順便了解一下go中格式化日期和時間的方式是很是奇葩的,格式化模板限定爲Mon Jan 2 03:04:05PM 2006 UTC-0700,能夠這樣記:1月2日下午3點4分5秒(200)6年UTC-0700。數據結構

  能夠用 nc 或 telnet 來鏈接這個tcp服務進行測試, 也可使用go實現一個簡單的客戶端:併發

func main() {
    conn, err := net.Dial("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    mustCopy(os.Stdout, conn)
}

func mustCopy(dst io.Writer, src io.Reader) {
    if _, err := io.Copy(dst, src); err != nil {
        log.Fatal(err)
    }
}

 

goroutine是經過 channel 來通訊的, 包括髮送和接收兩種操做。能夠經過 make 來聲明一個channel 如 ch = make(chan int),和 map 相似,channel 也只是對應底層數據結構的引用,因此發送方和接收方都將引用同一份對象,channel是引用類型,因此它的零值是 nil。channel 使用的操做符是 <- ,發送操做是指將數據發送到 channel,接收是指從 channel中接收。channel相似於通常的io系統,能夠經過 close(ch) 來關閉一個 channel,關閉後,將不能進行發送操做,但能夠接收以前未接收完的數據,若是沒有數據可接收,則接收到一個nil。tcp

 

經過 make(chan int) 這種方式建立的 channel 稱之爲無緩存channel,表示 channel的容量是0,若是要聲明一個有緩存的channel,可使用 make(chan int, 100) ,第二個參數表示初始化時的channel容量大小。函數

一個基於無緩存Channels的發送操做將致使發送者goroutine阻塞,直到另外一個goroutine在相同的Channels上執行接收操做,當發送的值經過Channels成功傳輸以後,兩個goroutine能夠繼續執行後面的語句。反之,若是接收操做先發生,那麼接收者goroutine也將阻塞,直到有另外一個goroutine在相同的Channels上執行發送操做。測試

簡單的說,就是使用無緩存channel時,一旦通訊發起,必需要等到通訊完成才能夠繼續執行,不然將阻塞等待。這也就意味着,無緩存channel在通訊時,會強制進行一次同步操做,因此無緩存channel也稱之爲同步channelspa

下面是一個串聯channel的例子:code

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        for x := 1; x <= 10; x++ {
            ch1 <- x
        }   
        close(ch1)
    }() 

    go func() {
        for x := range ch1 {
            ch2 <- x * x 
        }   
        close(ch2)
    }() 

    for x := range ch2 {
        fmt.Println(x)
    }   
}

無論一個channel是否被關閉,當它沒有被引用時將會被Go語言的垃圾自動回收器回收。(不要將關閉一個打開文件的操做和關閉一個channel操做混淆。對於每一個打開的文件,都須要在不使用的使用調用對應的Close方法來關閉文件。)試圖重複關閉一個channel或者關閉一個nil值的channel都將致使panic異常,此外關閉一個channels還會觸發一個廣播機制。orm

由於關閉操做只用於斷言再也不向channel發送新的數據,因此只有在發送者所在的goroutine纔會調用close函數,所以對一個只接收的channel調用close將是一個編譯錯誤。

單方向channel,只發送或只接收,功能明確。類型chan<- int表示一個只發送int的channel,只能發送不能接收。相反,類型<-chan int表示一個只接收int的channel,只能接收不能發送。(箭頭<-和關鍵字chan的相對位置代表了channel的方向。)這種限制將在編譯期檢測。

func counter(out chan<- int) {
    for x := 0; x < 100; x++ {
        out <- x
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for v := range in {
        out <- v * v
    }
    close(out)
}

func printer(in <-chan int) {
    for v := range in {
        fmt.Println(v)
    }
}

func main() {
    naturals := make(chan int)
    squares := make(chan int)
    go counter(naturals)
    go squarer(squares, naturals)
    printer(squares)
}

 

帶緩存的 channel,內部持有一個元素隊列。隊列的最大容量是在調用make函數建立channel時經過第二個參數指定的。向緩存Channel的發送操做就是向內部緩存隊列的尾部插入緣由,接收操做則是從隊列的頭部刪除元素。若是內部緩存隊列是滿的,那麼發送操做將阻塞直到因另外一個goroutine執行接收操做而釋放了新的隊列空間。相反,若是channel是空的,接收操做將阻塞直到有另外一個goroutine執行發送操做而向隊列插入元素。

內置的 cap 函數能夠查看某個 channel 的容量,len 函數能夠查看某個 channel 的當前緩存隊列中有效元素的個數。

 

若是咱們使用了無緩存的channel,那麼兩個慢的goroutines將會由於沒有人接收而被永遠卡住。這種狀況,稱爲goroutines泄漏,這將是一個BUG。和垃圾變量不一樣,泄漏的goroutines並不會被自動回收,所以確保每一個再也不須要的goroutine能正常退出是重要的。

關於無緩存或帶緩存channels之間的選擇,或者是帶緩存channels的容量大小的選擇,均可能影響程序的正確性。無緩存channel更強地保證了每一個發送操做與相應的同步接收操做;可是對於帶緩存channel,這些操做是解耦的。一樣,即便咱們知道將要發送到一個channel的信息的數量上限,建立一個對應容量大小帶緩存channel也是不現實的,由於這要求在執行任何接收操做以前緩存全部已經發送的值。若是未能分配足夠的緩衝將致使程序死鎖。

 

若是生產線的前期階段一直快於後續階段,那麼它們之間的緩存在大部分時間都將是滿的。相反,若是後續階段比前期階段更快,那麼它們之間的緩存在大部分時間都將是空的。對於這類場景,額外的緩存並無帶來任何好處。

 

個人理解是,當通訊雙方的工做效率接近時,緩存纔有意義,若是一方處理過快,而另外一方過慢,則效率由短板決定,緩存並不能起到提高效率的做用。

相關文章
相關標籤/搜索