1、channel簡介
go語言中併發程序非常簡單直接用關鍵字go就可以起一個協程,go語言的協程是語言級別的所以它的開銷比線程、進程要小,因爲線程和進程是系統級別的併發。go語言協程同步可以通過channel來解決。
channel有三種類型:
var aa chan int /*可讀可寫*/
var aa chan <- int /*只能寫*/
var aa <- chan int /*只能讀*/
channel類型必須先用make初始化再使用
aa := make(chan int)
關閉channel用函數close,一個關閉的channel還可以去讀數據,但不能在寫如數據,如果寫入數據就會產生panic
aa := make(chan int)
close(aa)
aa <- 4 /*panic*/
2、channel阻塞
對於buffer爲1的channel,從channel中讀數據時當沒有讀到就會阻塞,向channel中寫數據如果沒有空間存儲也會阻塞。
讀阻塞:
aa := make(chan int)
b := <- aa /*aa中沒有數據就會阻塞*/
寫阻塞:
aa := make(chan int)
aa <- 4
aa <- 5 /*4還沒有取出來,channel中沒有空間, 阻塞*/
3、channel非阻塞
當我們需要非阻塞的channel我們可以給channel分配空間,當空間沒有填滿channel不會阻塞,有buffer的channel相當於一個隊列,遵循先進先出的規則。
aa := make(chan int, 4)
帶buffer的channel讀取可以用range。但要注意的是當channel中的數據被取完後就會阻塞,如果沒有再往channel中寫數據就會產生死鎖
w := make(chan int, 4)
w <- 4
w <- 5
for b := range w {
/*最後一個5取走後就會阻塞,產生死鎖*/
fmt.Printf("w:%d\n", b)
}
避免死鎖方法:
每次range讀取數據後判斷channel的長度,如果是0就說明channel爲空,就應該退出。
w := make(chan int, 4)
w <- 1
w <- 2
w <- 3
w <- 4
for b := range w {
fmt.Printf("w:%d\n", b)
/*如果channel長度是0就退出循環,不然會阻塞產生死鎖*/
if len(w) <= 0 {
break
}
}
4、select
go中的select相當於c語言中的switch,是對對個channle做選擇,它不是循環,如果要循環判斷就要在外面在加一個for
package main
import (
"fmt"
"time"
)
func main() {
a := make(chan int)
b := make(chan int)
defer close(a)
defer close(b)
go func() {
for i := 1; i < 3; i++ {
a <- i
b <- (i + 1)
time.Sleep(1 * time.Second)
}
}()
for {
select {
case val1 := <-a:
fmt.Println("val1:", val1)
case val2 := <-b:
fmt.Println("val2:", val2)
case <-time.After(3 * time.Second): /*3秒超時*/
fmt.Println("time out quit")
return
}
}
}
select中可以加一個超時時間,超過這個時間通道中還沒有數據就直接返回。