goroutine
在go語言中,每個併發的執行單元叫作一個goroutinehtml
這裏說到併發,因此先解釋一下併發和並行的概念:編程
併發:邏輯上具有同時處理多個任務的能力緩存
並行:物理上在同一時刻執行多個併發任務安全
當一個程序啓動時,其主函數即在一個單獨的goroutine中運行,通常這個goroutine是主goroutine網絡
若是想要建立新的goroutine,只須要再執行普通函數或者方法的的前面加上關鍵字go併發
經過下面一個例子演示併發的效果,主goroutine會計算第45個斐波那契函數,在計算的同時會循環打印:-\|/ tcp
這裏須要知道:當主goroutine結束以後,全部的goroutine都會被打斷,程序就會退出函數
package main import ( "fmt" "time" ) func spinner(delay time.Duration) { for { for _, r := range `-\|/` { fmt.Printf("\r%c", r) time.Sleep(delay) } } } func fib(n int) int { //斐波那契數列 if n < 2 { return n } return fib(n-1) + fib(n-2) } func main() { go spinner(100 * time.Millisecond) const n = 45 fibN := fib(n) fmt.Printf("\rFib(%d)=%d\n", n, fibN) }
當第一次看到go的併發,感受真是太好用了!!!!單元測試
因此在網絡編程裏,服務端都是須要同時能夠處理不少個鏈接,咱們看一下下面的服務端和客戶端例子測試
服務端:
package main import ( "io" "log" "net" "time" ) func handleConn(c net.Conn) { defer c.Close() _, err := io.WriteString(c, time.Now().Format("15:04:05\r\n")) if err != nil { return } time.Sleep(1 * time.Second) } func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) return } for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } }
客戶端
package main import ( "io" "log" "net" "os" ) func mustCopy(dst io.Writer, src io.Reader) { // 從鏈接中讀取內容,並寫到標準輸出 if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } mustCopy(os.Stdout, conn) }
Channel
channel是不一樣的goroutine之間的通訊機制。
一個goroutine經過channel給另一個goroutine發送信息。
每一個channel 都有一個特殊的類型,也就是channel能夠發送的數據的類型
咱們能夠經過make建立一個channel如:
ch := make(chan int) 這就是建立了一個類型爲int的channel
默認咱們這樣建立的是無緩存的channel,固然咱們能夠經過第二個參數來設置容量
ch := make(chan int,10)
注意:channel是引用類型,channel的零值也是nil
兩個相同類型的channel可使用==運算符比較。若是兩個channel引用的是相通的對象,那麼比較的結
果爲真。一個channel也能夠和nil進行比較。
由於channel是在不一樣的goroutine之間進行通訊的,因此channel這裏有兩種操做:存數據和取數據,而這裏兩種操做的
方法都是經過運算符:<-
ch <- x 這裏是發送一個值x到channel中
x = <- ch 這裏是從channel獲取一個值存到變量x
<-ch 這裏是從channel中取出數據,可是不使用結果
close(ch) 用於關閉channel
當咱們關閉channel後,再次發送就會致使panic異常,可是若是以前發送過數據,咱們在關閉channel以後依然能夠執行接收操做
若是沒有數據的話,會產生一個零值
基於channel發送消息有兩個重要方面,首先每一個消息都有一個值,可是有時候通信的事件和發送的時刻也一樣重要。
咱們更但願強調通信發送的時刻時,咱們將它稱爲消息事件。有些消息並不攜帶額外的信息,它僅僅是用作兩個goroutine之間的同步,這個時候咱們能夠用struct{}空結構體做爲channel元素的類型
無緩存的channel
基於無緩存的channel的發送和接受操做將致使兩個goroutine作一次同步操做,因此無緩存channel有時候也被稱爲同步channel
串聯的channel (Pipeline)
channel也能夠用於多個goroutine鏈接在一塊兒,一個channel的輸出做爲下一個channel的輸入,這種串聯的channel就是所謂的pipeline
經過下面例子理解,第一個goroutine是一個計算器,用於生成0,1,2...形式的整數序列,而後經過channel將該整數序列
發送給第二個goroutine;第二個goroutine是一個求平方的程序,對收到的每一個整數求平方,而後將平方後的結果經過第二個channel發送給第三個goroutine
第三個goroutine是一個打印程序,打印收到的每一個整數
package main import ( "fmt" "time" ) func main() { naturals := make(chan int) squares := make(chan int) go func() { for x := 0;; x++ { naturals <- x } }() go func() { for { x := <-naturals squares <- x * x } }() for { fmt.Println(<-squares) time.Sleep(100 * time.Millisecond) } }
可是若是我把第一個生成數的寫成一個有範圍的循環,這個時候程序其實會報錯的。
把for x := 0;; x++改爲for x := 0; x < 100; x++,報錯以下:fatal error: all goroutines are asleep - deadlock!
因此就須要想辦法讓發送知道沒有能夠發給channel的數據了,也讓接受者知道沒有能夠接受的數據了
這個時候就須要用到close(chan)
當一個channel被關閉後,再向該channel發送數據就會致使panic異常
當從一個已經關閉的channel中接收數據,在接收完以前發送的數據後,並不會阻塞,而會馬上返回零值,因此在從channel裏接受數據的時候能夠多獲取一個值如:
go func(){ for { x ,ok := <-naturals if !ok{ break } squares <- x*x } close(squares) }()
第二位ok是一個布爾值,true表示成功從channel接受到值,false表示channel已經被關閉而且裏面沒有值能夠接收
單方向的channel
當一個channel做爲一個函數的參數時,它通常老是被專門用於只發送或者只接收
chan <- int :表示一個只發送int的channel,只能發送不能接收
< chan int : 表示一個只接受int的channel,只能接收不能發送
固然在有時候咱們須要獲取channel內部緩存的容量,能夠經過內置的cap函數獲取
而len函數則返回的是channel內實際有效的元素個數
基於select的多路複用
這裏先說一個擁有的知識點:time.Tick函數
這個函數返回一個channel,經過下面代碼進行理解:
package main import ( "fmt" "time" ) func main() { tick := time.Tick(1 * time.Second) for countDown := 10; countDown > 0; countDown-- { j := <-tick fmt.Println(j) } }
程序會循環打印一個時間戳
select 語句:
select { case <-ch1: // ... case x := <-ch2: // ...use x... case ch3 <- y: // ... default: // ... }
select語句的形式其實和switch語句有點相似,這裏每一個case表明一個通訊操做
在某個channel上發送或者接收,而且會包含一些語句組成的一個語句塊 。
select中的default來設置當其它的操做都不可以立刻被處理時程序須要執行哪些邏輯
channel 的零值是nil, 而且對nil的channel 發送或者接收操做都會永遠阻塞,在select語句中操做nil的channel永遠都不會被select到。
這可讓咱們用nil來激活或者禁用case,來達成處理其餘輸出或者輸出時間超時和取消的邏輯
補充:channel 概念
相似unix中的管道pipe
先進先出
線程安全,多個goroutine同時訪問,不須要加鎖
channel是有類型的,一個整數的channel只能存放
定時器小例子:
//定時器 package main import ( "time" "fmt" ) func main() { t := time.NewTicker(time.Second) for v:= range t.C{ fmt.Println("hello",v) } }
// 一次性定時器 package main import ( "time" "fmt" ) func main() { select{ case <- time.After(time.Second): fmt.Println("after") } }
//超時控制
package main import ( "time" "fmt" ) func queryDb(ch chan int){ time.Sleep(time.Second) ch <- 100 } func main() { ch := make(chan int) go queryDb(ch) t := time.NewTicker(time.Second*4) select{ case v:=<-ch: fmt.Println("result:",v) case <-t.C: fmt.Println("timeout")
補充:不一樣的goroutine之間如何通訊
首先咱們可以想到的有:全局變量的方式,咱們先經過這種本方法來演示:
package main import ( "time" "fmt" ) var exits [3]bool func calc(index int){ for i:=0;i<1000;i++{ time.Sleep(time.Millisecond) } exits[index] = true } func main() { start := time.Now().UnixNano() go calc(0) go calc(1) go calc(2) for{ if exits[0] && exits[1] &&exits[2]{ break } } end := time.Now().UnixNano() fmt.Println("finished,const:%d ms",end-start) }
這種方法其實比較笨,go爲咱們提供了鎖同步的方式 sync.WaitGroup,演示代碼爲:
//等待一組goroutine執行完成 package main import ( "time" "fmt" "sync" ) var waitGroup sync.WaitGroup func calc(index int){ for i:=0;i<1000;i++{ time.Sleep(time.Millisecond) } //執行完成的時候Done waitGroup.Done() } func main() { start := time.Now().UnixNano() for i:=0;i<3;i++{ // 每次在調用以前add waitGroup.Add(1) go calc(i) } //在循環外等待wait waitGroup.Wait() end := time.Now().UnixNano() fmt.Println("finished,const:%d ms",end-start) }
補充:關於單元測試和異常捕獲
package main import ( "time" "fmt" ) func calc(){ // defer 定義的後面出現錯誤的均可以捕獲到 defer func() { err := recover() if err!=nil{ fmt.Println(err) } }() var p *int *p = 100 }