Go語言中併發程序能夠用兩種方式來實現。一種是goroutine和channel,其支持「順序進程通訊」(communicating sequential processes)或被簡稱爲CSP。CSP是一個現代的併發編程模型,在這種編程模型中值會在不一樣的運行實例(goroutine)中傳遞,儘管大多數狀況下被限制在單一實例中。另外一種是傳統的併發模型,多線程共享內存(基於共享變量的併發),會在後續單獨闡述。編程
goroutine是一個輕量級的執行線程(又稱協程),它與線程的區別是線程是操做系統中對於一個獨立運行實例的描述,不一樣的操做系統中,線程的實現也不盡相同;對於goroutine,操做系統並不知道它的存在,goroutine的調度是Go語言的運行時進行管理的。啓動線程雖然比進程使用的資源少,但依然須要上下文切換等大量工做,Go語言有本身的調度器,許多goroutine的數據都是共享的,所以goroutine之間的切換會快不少,啓動goroutine所耗費的資源也不多。緩存
package main
import (
"fmt"
"strconv"
)
func Info(name string) {
for i := 0; i < 3; i++ {
fmt.Println(name + ":" + strconv.Itoa(i))
}
}
func main() {
Info("info")
go Info("goroutine1")
go func(name string) {
fmt.Println(name)
}("goroutine2")
var input string
fmt.Scanln(&input)
fmt.Println("done")
}
複製代碼
channel被稱爲通道,是鏈接併發goroutine的管道,能夠從一個goroutine向通道發送值,並在另外一個goroutine中接收到這些值。每一個channel都有一個特殊的類型,也就是channel可發送數據的類型。一個能夠發送int類型數據的channel通常寫爲chan int。多線程
一個channel有發送和接收兩個主要操做,都是通訊行爲。一個發送語句將一個值從一個goroutine經過channel發送到另外一個執行接收操做的goroutine。發送和接收兩個操做都是用<-
運算符。在發送語句中,<-
運算符分割channel和要發送的值;在接收語句中,<-
運算符寫在channel對象以前,一個不使用接收結果的接收操做也是合法的。併發
channel的發送操做將致使發送者goroutine阻塞,直到另外一個goroutine在相同的channel上執行接收操做,當發送的值經過channel成功傳輸以後,兩個goroutine能夠繼續執行後面的語句。反之,若是接收操做先發生,那麼接收者goroutine也將阻塞,直到有另外一個goroutine在相同的Channels上執行發送操做。函數
package main
import (
"fmt"
"strconv"
)
func Info(i int, ch chan string) {
msg := "數據" + strconv.Itoa(i)
ch <- msg
}
func main() {
chs := make([]chan string, 3)
for i := 0; i < 3; i++ {
chs[i] = make(chan string)
go Info(i, chs[i])
}
for num, ch := range chs {
msg := <- ch
fmt.Println(num, msg)
}
fmt.Println("Done")
}
複製代碼
channel還支持close操做,用於關閉channel,隨後對基於該channel的任何發送操做都將致使Panic異常。對一個已經被close過的channel接收操做依然能夠接受到以前已經成功發送的數據;若是channel中已經沒有數據的話將產生一個零值(nil)的數據。ui
//使用內置的close函數就能夠關閉一個channel:
close(ch)
複製代碼
帶緩存的channel內部有一個元素隊列。隊列的最大容量是在調用make函數建立channel時經過第二個參數指定的。下面的語句建立了一個能夠持有三個字符串元素的帶緩存channel。spa
ch = make(chan string, 3)
複製代碼
向緩存channel的發送操做就是向內部緩存隊列的尾部插入元素,接收操做則是從隊列的頭部刪除元素。若是內部緩存隊列是滿的,那麼發送操做將阻塞直到另外一個goroutine執行接收操做而釋放了新的隊列空間。相反,若是channel是空的,接收操做將阻塞直到有另外一個goroutine執行發送操做而向隊列插入元素。操作系統
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "A"
ch <- "B"
ch <- "C"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
複製代碼
Go語言的select的功能和select、poll、epoll類似,就是監聽IO操做,當IO操做發生時,觸發響應的動做。select語句的用法和switch類似,也會有幾個case和default分支,每個case表明一個通訊操做(在某個channel上進行發送或者接收)而且會包含一些語句組成一個語句塊。線程
package main
import (
"fmt"
"strconv"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch1 <- "I am from ch1"
}()
go func() {
ch2 <- "I am from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <- ch1:
fmt.Println("received-"+strconv.Itoa(i), msg1)
case msg2 := <- ch2:
fmt.Println("received-"+strconv.Itoa(i), msg2)
}
}
}
複製代碼
select是用來讓咱們的程序監聽多個文件句柄的狀態變化的處理機制。當發起一些阻塞的請求後,能夠用select機制輪詢掃描文件句柄,直到被監視的文件句柄有一個或多個發生了狀態改變。channel在系統層面來講也是個文件描述符,在Go語言中咱們能夠用goroutine併發執行任務,接着使用select來監視每一個任務的channel狀況。若是這幾個任務都長時間沒有回覆channel信息,而且咱們又有超時的需求,那麼咱們可使用一個goroutine來設置超時機制,具體作法就是啓動sleep而且在sleep以後回覆channel信號。code
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
timeout := make(chan bool)
go func() {
time.Sleep(5 * time.Second)
timeout <- true
}()
go func() {
time.Sleep(10 * time.Second)
ch <- "Hello World"
}()
select {
case msg := <- ch:
fmt.Println(msg)
case <- timeout:
fmt.Println("task is timeout")
}
}
複製代碼