golang 學習 ---- channel

把一個loop放在一個goroutine裏跑,咱們可使用關鍵字go來定義並啓動一個goroutine:python

package main

import "fmt"

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}
}

func main() {
	go loop() // 啓動一個goroutine
	loop()
}

輸出:函數

0 1 2 3 4 5 6 7 8 9

 

但是爲何只輸出了一趟呢?明明咱們主線跑了一趟,也開了一個goroutine來跑一趟啊。oop

原來,在goroutine還沒來得及跑loop的時候,主函數已經退出了。測試

main函數退出地太快了,咱們要想辦法阻止它過早地退出,一個辦法是讓main等待一下:ui

package main

import (
	"fmt"
	"time"
)

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}
}

func main() {
	go loop() // 啓動一個goroutine
	loop()
	time.Sleep(time.Second)
}

  

但是採用等待的辦法並很差,若是goroutine在結束的時候,告訴下主線說「Hey, 我要跑完了!」就行了, 即所謂阻塞主線的辦法,回憶下咱們Python裏面等待全部線程執行完畢的寫法:操作系統

for thread in threads:
    thread.join()

  

是的,咱們也須要一個相似join的東西來阻塞住主線。那就是信道(channel).net

channel是goroutine之間互相通信的東西。相似咱們Unix上的管道(能夠在進程間傳遞消息), 用來goroutine之間發消息和接收消息。其實,就是在作goroutine之間的內存共享。線程

使用make來創建一個信道:code

var channel chan int = make(chan int)
// 或
channel := make(chan int)

  

那如何向信道存消息和取消息呢? 一個例子:blog

package main

import (
	"fmt"
)

func main() {
	var msg chan string = make(chan string)//無緩衝channel
	go func(message string) {
		msg <- message // 存消息
	}("Ping!")

	fmt.Println(<-msg) // 取消息
}

  

默認的,信道的存消息和取消息都是阻塞的 (叫作無緩衝的信道,不過緩衝這個概念稍後瞭解,先說阻塞的問題)。

也就是說, 無緩衝的信道在取消息和存消息的時候都會掛起當前的goroutine,除非另外一端已經準備好。

好比如下的main函數和foo函數:

package main

var ch chan int = make(chan int)

func foo() {
    ch <- 0  // 向ch中加數據,若是沒有其餘goroutine來取走這個數據,那麼掛起foo, 直到main函數把0這個數據拿走
}

func main() {
    go foo()
    <- ch // 從ch取數據,若是ch中還沒放數據,那就掛起main線,直到foo函數中放數據爲止
}

那既然信道能夠阻塞當前的goroutine, 那麼回到上一部分「goroutine」所遇到的問題「如何讓goroutine告訴主線我執行完畢了」 的問題來, 使用一個信道來告訴主線便可:

package main

import "fmt"

var complete chan int = make(chan int)

func loop() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", i)
	}

	complete <- 0 // 執行完畢了,發個消息
}

func main() {
	go loop()
	<-complete // 直到線程跑完, 取到消息. main在此阻塞住
}

  

若是不用信道來阻塞主線的話,主線就會過早跑完,loop線都沒有機會執行、、、

其實,無緩衝的信道永遠不會存儲數據,只負責數據的流通,爲何這麼講呢?

  • 從無緩衝信道取數據,必需要有數據流進來才能夠,不然當前線阻塞

  • 數據流入無緩衝信道, 若是沒有其餘goroutine來拿走這個數據,那麼當前線阻塞

因此,你能夠測試下,不管如何,咱們測試到的無緩衝信道的大小都是0 (len(channel))

若是信道正有數據在流動,咱們還要加入數據,或者信道乾澀,咱們一直向無數據流入的空信道取數據呢? 就會引發死鎖

死鎖

一個死鎖的例子:

package main

func main() {
	ch := make(chan int)
	<-ch // 阻塞main goroutine, 信道c被鎖
}

執行這個程序你會看到Go報這樣的錯誤:

fatal error: all goroutines are asleep - deadlock!

何謂死鎖? 操做系統有講過的,全部的線程或進程都在等待資源的釋放。如上的程序中, 只有一個goroutine, 因此當你向裏面加數據或者存數據的話,都會鎖死信道, 而且阻塞當前 goroutine, 也就是全部的goroutine(其實就main線一個)都在等待信道的開放(沒人拿走數據信道是不會開放的),也就是死鎖咯。

我發現死鎖是一個頗有意思的話題,這裏有幾個死鎖的例子:

只在單一的goroutine裏操做無緩衝信道,必定死鎖。好比你只在main函數裏操做信道:

package main

import "fmt"

func main() {
	ch := make(chan int)
	ch <- 1                                // 1流入信道,堵塞當前線, 沒人取走數據信道不會打開
	fmt.Println("This line code wont run") //在此行執行以前Go就會報死鎖
}

主線等ch1中的數據流出,ch1等ch2的數據流出,可是ch2等待數據流入,兩個goroutine都在等,也就是死鎖

package main

import "fmt"

var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)

func say(s string) {
	fmt.Println(s)
	ch1 <- <-ch2 // ch1 等待 ch2流出的數據
}

func main() {
	go say("hello")
	<-ch1 // 堵塞主線
}

  總結來看,爲何會死鎖?非緩衝信道上若是發生了流入無流出,或者流出無流入,也就致使了死鎖。或者這樣理解 Go啓動的全部goroutine裏的非緩衝信道必定要一個線裏存數據,一個線裏取數據,要成對才行 。因此下面的示例必定死鎖:

package main

func main() {
	c, quit := make(chan int), make(chan int)

	go func() {
		c <- 1    // c通道的數據沒有被其餘goroutine讀取走,堵塞當前goroutine
		quit <- 0 // quit始終沒有辦法寫入數據
	}()

	<-quit // quit 等待數據的寫
}

  仔細分析的話,是因爲:主線等待quit信道的數據流出,quit等待數據寫入,而func被c通道堵塞,全部goroutine都在等,因此死鎖。

修正死鎖
package main

func main() {
	c, quit := make(chan int), make(chan int)

	go func() {
		c <- 1    // c通道的數據沒有被其餘goroutine讀取走,堵塞當前goroutine
		quit <- 0 // quit始終沒有辦法寫入數據
	}()

	go func() {
		<-c
		<-quit
	}()

}

  

 

給channel增長緩衝區,而後在程序的最後讓主線程休眠一秒,代碼以下:

package main

import (
	"fmt"
	"time"
)

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

}
相關文章
相關標籤/搜索