把一個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") }