Golang select

select的做用

Go裏面提供了一個關鍵字 select, 經過 select 能夠監聽channel上的數據流動.git

select 的用法與 switch 語言很是相似, 由 select 開始一個新的選擇塊, 每一個選擇條件由 case 語句來描述.網站

switch 語句相比, select 有比較多的限制, 其中最大的一條限制就是每一個case語句裏必須是一個IO操做.ui

大體的結構以下:code

select {
case <- chan1:
	// 若是chan1成功讀到數據, 則進行該case處理語句 
case chan2 <- -1:
	// 若是成功向chan2寫入數據, 則進行該case處理語句
default:
	// 若是上面都沒有成功, 則進入default處理流程
}

在一個 select 語句中, Go語言會按照順序從頭到尾評估每個發送和接收的語句.事件

若是其中的任意一條語句能夠繼續執行(即沒有阻塞), 那麼就從那些能夠執行的語句中任意選擇一條來使用.ci

若是沒有任意一條語句能夠執行(即全部的通道都被阻塞), 那麼有兩種可能的狀況:get

  • 若是給出了default語句, 那麼就會執行default語句, 同時程序的執行會從select語句後的語句中恢復.
  • 若是沒有default語句, 那麼select語句將被阻塞, 直到至少有一個通訊能夠進行下去.

select的基本使用

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	ch := make(chan int)  // 用來進行數據通訊的channel
	quit := make(chan bool)  // 用來判斷是否退出的channel

	go func() {  // 寫數據
		for i:=0; i < 5; i++ {
			ch <- i
			time.Sleep(time.Second)
		}
		close(ch)
		quit <- true  // 通知主go程 退出
		runtime.Goexit()
	}()

	for {
		select {
		case num := <- ch:
			fmt.Println("讀到: ", num)
		case <- quit:
			return
			//break  // break 跳出select循環
		}
		fmt.Println("============")
	}
}

結果:博客

讀到:  0
============
讀到:  1
============
讀到:  2
============
讀到:  3
============
讀到:  4
============
讀到:  0
============
讀到:  0
============

注意, 由於是任意挑選一個case執行, 因此最後的 讀到:0 的數量至關因而個隨機數.it

因此, 總結下select的注意事項:for循環

  • case後面必須是IO操做, 不能夠是判別表達式.
  • 監聽的case中, 沒有知足監聽條件, 阻塞.
  • 監聽的case中, 有多個知足監聽條件, 任選一個執行.
  • 可使用default來處理全部case都不知足監聽條件的情況.(一般不用, 會產生 忙輪詢)
  • select自身不帶有循環機制, 需藉助外層for循環來進行循環監聽
  • break只能跳出select. 相似於switch中的用法.

select實現斐波那契數列

package main

import (
	"fmt"
	"runtime"
)

func fibonacci(ch <-chan int, quit <-chan bool) {
	for {
		select {
		case num := <-ch:
			fmt.Println(num)
		case <-quit:
			//return
			runtime.Goexit()
		}
	}
}

func main() {
	ch := make(chan int)
	quit := make(chan bool)

	go fibonacci(ch, quit)

	x, y := 1, 1
	for i := 0; i < 50; i++ {
		ch <- x
		x, y = y, x+y
	}
	quit <- true
}

爲何要用到select?

若是不用select的話, 每個case都要建立一個go程去處理, 這樣的話太浪費了, 而用select的話, 只須要一個go程就能夠了.

超時

有時候會出現goroutine阻塞的狀況, 那麼咱們如何避免整個程序進入阻塞的狀況呢?咱們能夠利用select來設置超時, 經過以下的方式來實現:

示例代碼:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	timeOut := make(chan bool)

	go func() {
		for {
			select {
			case num := <- ch:
				fmt.Println("num: ", num)
			case <- time.After(5 * time.Second):
				fmt.Println("timeout")
				timeOut <- true
				return
			}
		}
	}()
	ch <- 666
	<- timeOut  // 主go程, 阻塞等待子go程通知, 退出
	fmt.Println("finish.")
}

select監聽time.After() 中channel的讀事件, 若是定時時間到, 系統會向該channel中寫入系統當前時間.

李培冠博客

歡迎訪問個人我的網站:

李培冠博客:lpgit.com

相關文章
相關標籤/搜索