golang-goroutine和channel

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 }

 

轉自https://www.cnblogs.com/zhaof/p/8393091.html
相關文章
相關標籤/搜索