Golang select


select的作用

Go里面提供了一個關鍵字 select, 通過 select 可以監聽channel上的數據流動.

select 的用法與 switch 語言非常類似, 由 select 開始一個新的選擇塊, 每個選擇條件由 case 語句來描述.

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

大致的結構如下:

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

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

如果其中的任意一條語句可以繼續執行(即沒有阻塞), 那么就從那些可以執行的語句中任意選擇一條來使用.

如果沒有任意一條語句可以執行(即所有的通道都被阻塞), 那么有兩種可能的情況:

  • 如果給出了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 的數量相當於是個隨機數.

所以, 總結下select的注意事項:

  • 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM