o 語言有一個很重要的特性就是 goroutine, 咱們可使用 goroutine 結合 channel 來開發併發程序。緩存
併發程序指的是能夠同時運行多個任務的程序,這裏的同時運行並不必定指的是同一時刻執行,在單核CPU的機器下,在同一時刻只可能有一個任務在執行,可是因爲CPU的速度很快,在不斷的切換着多個任務,讓它們交替的執行,所以宏觀上看起來就像是同時在運行; 而在多核的機器上,併發程序中的多個任務是能夠實如今同一時刻執行多個的,此時併發的多個任務是在並行執行的。安全
goroutine 是 go 語言中的併發執行單元,咱們能夠將多個任務分別放在多個 goroutine 中,來實現併發程序。下面先看一個例子:數據結構
package main import "fmt" func hello() { fmt.Println("Hello World!!!") } func main() { go hello() fmt.Println("Bye!!!") var input string fmt.Scanln(&input) }
上述程序的執行結果以下:併發
Bye!!! Hello World!!!
上面這個例子展現了使用 goroutine 的幾個要點:函數
go hello()
就是用於建立一個 goroutine, 即 go 關鍵字加上 要在 goroutine 中執行的函數(也能夠是匿名函數,不過必須是調用的形式)以上就是 goroutine 的基本用法學習
前面咱們學習了怎樣建立並行的執行單元,可是每一個執行單元之間是徹底獨立的,若是咱們想在運行期間交換數據,即進行通訊,此時就得依靠另外一個概念 - channels, 即通道,這個名字十分貼切,就像在不一樣的併發執行單元之間鏈接了一根管道,而後經過這跟管道來發送和接收數據。code
goroutine 和 channel 常常結合在一塊兒使用,下面學習一些 channel 的用法:開發
建立 channel字符串
ch1 := make(chan int)
channel 也須要使用 make 函數來建立,也就是說 channel 也是一種引用類型(make函數會返回低層數據結構的引用給channel)input
向 channel 中讀寫數據
前面說了 channel 是用於 goroutine 之間通訊的, 天然可以從 channel 中寫入和讀取數據,使用的都是 <-
操做符
ch := make(chan int) ch<- 1 // 向 channel 中寫入數據 var a int = <-ch // 從 channel 中讀取數據
關閉 channel
在咱們使用完一個 channel 以後,能夠調用 close() 方法來關閉一個 channel, 關閉以後的通道,不可以再進行數據的寫操做, 可是仍然能夠讀取以前寫入成功的數據(若是沒有數據了,將返回零值)。
channel 的基本操做就是上面這麼多,不過實際上,channel 是有兩種的: 無緩衝的 和 有緩衝的。上面咱們建立的是無緩存的,有緩存的建立方式是 ch := make(chan int, 2)
, 兩者的區別是:
例1: 通道用於傳遞消息
package main import "fmt" func main() { message := make(chan string) // 建立一個用於傳遞字符串的通道 go func() { message <- "This is a message." // 向 channel 寫入數據 }() msg := <- message // 從 channel 讀取數據 fmt.Println(msg) }
例2: 利用通道進行同步
package main import "fmt" func hello() { fmt.Println("Hello World!!!") done <- true } func main() { done := make(chan bool) go hello() fmt.Println("Bye!!!") <-done // 這裏會阻塞住,直到在另外一個 goroutine 中對 done 進行寫入操做以後 }
當使用 channel 做爲參數,咱們能夠指定 channel 爲單向的,即讓通道在函數中只能發送,或者只能接收數據,以此來提升程序的安全性.
語法:
<-chan type
表示一個只能接收數據的通道chan<- type
表示一個只能發送數據的通道例子:
package main import "fmt" // 這裏的 message 在函數 send 中就是一個只能發送數據的通道 func send(msg string, message chan<- string) { message<- msg } // 這裏的 message 在函數 receive 中就是一個只能發送數據的通道 func receive(message <-chan string) string { msg := <- message return msg } func main() { message := make(chan string) go send("hello", message) fmt.Println(receive(message)) }
輸出結果是 hello
, 此時在函數 send 中,message 通道就只能用於發送數據,而在函數 receive 中通道只能接收數據,經過參數的限制使其在函數內部成爲了單向的通道。
go語言提供了一個 select 關鍵字,可使用它來等待多個通道的操做,以實現多路複用。語法:
select { case <-ch1: ... case ch2 <- value: ... default: ... }
其中的每一個 case 表示一個 channel 的操做,當case語句後面指定通道的操做能夠執行時,select 纔會執行 case 以後的語句。此時其餘的語句都不會被執行。
例子: 超時處理
package main import "time" import "fmt" func main() { ch1 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) ch1 <- "result 1" }() select { case res := <- ch1: fmt.Println(res) case <-time.After(time.Second * 1): fmt.Println("timeout 1") } ch2 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) ch2 <- "result 2" }() select { case res := <-ch2: fmt.Println(res) case <-time.After(time.Second * 3): fmt.Println("timeout 2") } }
上面的例子中咱們定義了兩個通道和兩個select結構,是爲了進行對比,第一個channel會在等待兩秒以後被寫入數據,而在 select 中,第二個case語句只會等待一秒,而後就會執行,所以就會執行超時操做。而在第二個 select 中,第二個 case 語句會等待三秒。因此上述程序的結果以下:
timeout 1 result 2