1、selectgit
Go語言引入了select關鍵字,其語法與switch很是相似,先看一個switch例子:
編程
func main() {併發 var a int = 1ide switch {函數 case a == 1:ui fmt.Println("ok")lua case a == 2:spa fmt.Println("no ok")命令行 default:3d fmt.Println("default") } } |
運行該程序,能夠正常打印出「ok」
下面把switch替換爲select:
func main() { var a int = 1 select{ case a == 1: fmt.Println("ok") case a == 2: fmt.Println("no ok") default: fmt.Println("default") } } |
運行該程序拋出錯誤:a == 1 evaluated but not used
這是爲什麼?是由於select中的case語句必須是一個IO操做!!!
即:switch中的case語句判斷條件只要能比較就行,而select中的case語句判斷條件必須是一個IO操做。
咱們在《Go語言的併發》中說過Channel,從Channel中讀取值和向Channel中寫入值對應的操做都是IO操做。下面咱們寫一個關於select的例子:
func setValue(ch1 chan int, ch2 chan string) { ch1 <- 1 ch2 <- "goroutine" } |
定義一個函數setValue(),入參爲兩個channel,該方法體內向channel 1中寫入一個×××值、向channel 2中寫入一個字符串;在併發章節咱們說過:「當入覺得channel時,就不是值傳遞了,而變成一個地址傳遞」。
func main() { var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") default: fmt.Println("default") } } |
先定義兩個channel類型的變量,使用make對其初始化;而後使用go關鍵字拉啓一個goroutine;main所在goroutine繼續向下走,開始執行select。
執行一下程序:
發現只打印了一個「default」,而沒有打印「ch1 ok」或者「ch2 ok」,多執行幾回結果同樣,從併發的角度上考慮機率狀況,這是不正常的。
您可能會想向ch一、ch2中寫入數據,屬於IO操做,可能會慢一些,當select執行完default時,IO操做依舊沒有完成。咱們驗證一下,修改代碼:
func main() { var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
這裏把default刪除掉了,當執行到select時,程序會查看各個分支,因爲沒有default分支,此時若channel中沒有內容,則main所在的goroutin會阻塞,至到ch1或者ch2中有內容爲至。
執行一下該程序:
發現成功打印出"ch1 ok」,多運行幾回依舊打印出「ch1 ok」,從沒有打印出「ch2 ok」,這是爲何呢?
看過我前面章節的可能會想,因爲目前使用的是go1.4版本,它並不支持多核併發,加之GO語言在執行時更傾向先讓一個goroutine執行完(即咱們常說的讓領導先走:) ),下面咱們再修改一下程序:
func main() { runtime.GOMAXPROCS(runtime.NumCPU()) // 強制Go進行多核併發 var ch1 chan int = make(chan int) var ch2 chan string = make(chan string) go setValue(ch1, ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
再多運行幾回程序:
從運行結果上來看,多核也沒有解決這個問題,這是由於當執行到select時,它發現ch一、ch2均無內容,程序發生阻塞,直到另外的goroutine把數據寫入ch1或ch2,因爲在另外的goroutine中ch1老是第一個被寫入數據,因此main所在的gorouinte老是先從ch1中獲取到數據,從而打印「ch1 ok」以後就退出了!
那如何完善這個程序呢?
func setValue(ch chan int){ ch <- 1 } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) var ch1, ch2 chan int = make(chan int), make(chan int) go setValue(ch1) go setValue(ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") } } |
多運行幾回看一下結果:
從結果上來看,達到了預期目的!
2、死鎖
如上面的setValue()方法所示,入參是一個channel,方法體是向該channel中寫入數據,因爲channel做爲入參是一個地址傳遞,因此在select中的case始終能從channel中讀取到數據。
試想若程序猿把setValue()中賦值忘記了呢?以下:
func setValue(ch chan int) { // ch <- 1 註釋掉該行 } |
運行一下發現系統報了死鎖:
像這種狀況是在所不免的,若是避免死鎖這類問題呢?
有一種辦法是引入另外一個超時Channel,另啓一個goroutine先讓它休息必定時間(超時時間),而後把數據寫入該Channel,代碼以下:
package main import ( "fmt" "runtime" "time" ) func setValue(ch chan int) { //ch <- 1 讓ch一、ch2產生死鎖 } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) var timeout chan bool = make(chan bool) // 建立一個超時的Channel go func() { // 新建立一個goroutine time.Sleep(time.Second * 10) // 休息10秒 timeout <- true // 在10秒內ch一、ch2若尚未向裏面寫入數據,則認爲超時 }() // 加一個()的意思是讓這個gorouinte執行 var ch1, ch2 chan int = make(chan int), make(chan int) go setValue(ch1) go setValue(ch2) select { case <-ch1: fmt.Println("ch1 ok") case <-ch2: fmt.Println("ch2 ok") case <-timeout: // 若ch一、ch2死鎖,10秒鐘後timeout填充數據,避免死鎖 fmt.Println("Timeout coming...") } } |
運行一下程序:
3、有意思的程序
啥話都別說了,直接上代碼:
package main import ( "fmt" ) func main() { var ch chan int = make(chan int, 1) for { select { case ch <- 0: case ch <- 1: } fmt.Printf("%d", <-ch) } } |
看懂了沒有?
解釋一下:
一、先定義一個類型爲int的Channel
二、再來一個死循環
三、使用select關鍵字進行選擇
四、case的後面是分別向ch寫入0或者1
五、使用Printf進行打印結果
在命令行窗口或者git中執行一下該程序,結果以下:
打印出一連串的隨機數
這是爲何呢?
select {
case ch <- 0:
case ch <- 1:
}
這句話的意思就是向channel中放置數據爲0或者1的隨機數.