Go基礎--goroutine和channel
goroutine
在go語言中,每一個並發的執行單元叫做一個goroutine
這裏說到並發,所以先解釋一下並發和並行的概念:
並發:邏輯上具備同時處理多個任務的能力
並行:物理上在同一時刻執行多個並發任務
當一個程序啟動時,其主函數即在一個單獨的goroutine中運行,一般這個goroutine是主goroutine
如果想要創建新的goroutine,只需要再執行普通函數或者方法的的前面加上關鍵字go
通過下面一個例子演示並發的效果,主goroutine會計算第45個斐波那契函數,在計算的同時會循環打印:-\|/
這裏需要知道:當主goroutine結束之後,所有的goroutine都會被打斷,程序就會退出
package main import ( "time" "fmt" ) func spinner(delay time.Duration){ for { for _,r := range `-\|/`{ fmt.Printf("\r%c",r) time.Sleep(delay) } } } func fib(x int) int{ // 斐波那契函數 if x < 2{ return x } return fib(x-1) + fib(x-2) } func main() { go spinner(100*time.Millisecond) //開啟一個goroutine const n = 45 fibN:= fib(n) fmt.Printf("\rFib(%d) = %d\n",n,fibN) }
當第一次看到go的並發,感覺真是太好用了!!!!
所以在網絡編程裏,服務端都是需要同時可以處理很多個連接,我們看一下下面的服務端和客戶端例子
服務端:
package main import ( "net" "io" "time" "log" ) func handleConn(c net.Conn){ defer c.Close() for{ _,err := io.WriteString(c,time.Now().Format("15:04:05\r\n")) if err != nil{ return } time.Sleep(1*time.Second) } } func main() { // 監聽本地tcp的8000端口 listener,err := net.Listen("tcp","localhost:8000") if err != nil{ log.Fatal(err) } 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) } defer conn.Close() 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" ) 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) } }
但是如果我把第一個生成數的寫成一個有範圍的循環,這個時候程序其實會報錯的
所以就需要想辦法讓發送知道沒有可以發給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 ( "time" "fmt" ) 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,來達成處理其他輸出或者輸出時間超時和取消的邏輯
Go基礎--goroutine和channel