Go Select使用


 Go Select使用

Go中的select和channel配合使用,通過select可以監聽多個channel的I/O讀寫事件,當 IO操作發生時,觸發相應的動作。

基本用法

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

使用規則

1.如果沒有default分支,select會阻塞在多個channel上,對多個channel的讀/寫事件進行監控。
2.如果有一個或多個IO操作可以完成,則Go運行時系統會隨機的選擇一個執行,否則的話,如果有default分支,則執行default分支語句,如果連default都沒有,則select語句會一直阻塞,直到至少有一個IO操作可以進行。   

快速返回

同時監聽不同的channel,做同一件工作,可以最快的返回結果。

package main

import (
	"fmt"
	"github.com/kirinlabs/HttpRequest"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan int)
	go Getdata("https://www.baidu.com",ch1)
	go Getdata("https://www.baidu.com",ch2)
	go Getdata("https://www.baidu.com",ch3)
	select{
		case v:=<- ch1:
			fmt.Println(v)
		case v:=<- ch2:
			fmt.Println(v)
		case v:=<- ch3:
			fmt.Println(v)
	}
}

func Getdata(url string,ch chan int){
	req,err := HttpRequest.Get(url)
	if err != nil{

	}else{
		ch <- req.StatusCode()
	}
}

隨機返回

同時監控不同的channel,配上default,select也不會阻塞。

package main

import (
	"fmt"
	"github.com/kirinlabs/HttpRequest"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan int)
	go func(){
		for {
			Getdata("https://www.baidu.com", ch1)
			Getdata("https://cn.bing.com", ch2)
			Getdata("https://cn.bing.com", ch3)
		}
	}()
	go func(){
		for {
			select {
				case v := <-ch1:
					fmt.Println("信道1的結果:",v)
				case v := <-ch2:
					fmt.Println("信道2的結果:",v)
				case v := <-ch3:
					fmt.Println("信道3的結果:",v)
				default:
					continue
			}
		}
	}()
	select{}
}

func Getdata(url string,ch chan int){
	req,err := HttpRequest.Get(url)
	if err != nil{

	}else{
		ch <- req.StatusCode()
	}
}

通過select來檢測channel的關閉事件

func TestSelect1() {
    start := time.Now()
    c := make(chan interface{})

    go func() {
        time.Sleep(2*time.Second)
        close(c)
    }()

    fmt.Println("Blocking on read...")
    select {
    case <-c:
        fmt.Printf("Unblocked %v later.\n", time.Since(start))
    }
}

注意:當close channel時,讀取channel的一方會從channel中讀取到value,false,此時的value一般情況下為nil。
該例子也可以用來通知當不使用channel時,關閉channel的情況。 

通過channel通知,從而退出死循環

func TestExitLoop() {
    done := make(chan interface{})

    go func() {
        time.Sleep(2*time.Second)
        close(done)
    }()

    workCounter := 0
loop:
    for {
        select {
        case <-done:
            break loop
        default:
        }

        // Simulate work
        workCounter++
        time.Sleep(1*time.Second)
    }

    fmt.Printf("在通知退出循環時,執行了%d次.\n", workCounter)
}

啟動一個goroutine,該goroutine在2s后,關閉channel。此時,主協程會在select中的case <-done分支中得到通知,跳出死循環。而在此之前,會執行default分支的代碼,這里是什么都不做。

超時機制

package main
import (
	"fmt"
	"time"
)
func main() {
	ch := make(chan int)
	quit := make(chan bool)
	//新開一個協程
	go func() {
		for {
			select {
			case num := <-ch:
				fmt.Println("num = ", num)
			case <-time.After(3 * time.Second):
				fmt.Println("超時")
				quit <- true
			}
		}
	}() 
	for i := 0; i < 5; i++ {
		ch <- i
		time.Sleep(time.Second)
	}
	<-quit
	fmt.Println("程序結束")
}

死鎖與默認情況

package main

func main() {  
    ch := make(chan string)    
    select {    
        case <-ch:
    }
}

上面的程序中,我們在第 4 行創建了一個信道 ch。我們在 select 內部(第 6 行),試圖讀取信道 ch。由於沒有 Go 協程向該信道寫入數據,因此 select 語句會一直阻塞,導致死鎖。該程序會觸發運行時 panic,報錯信息如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox416567824/main.go:6 +0x80

如果存在默認情況,就不會發生死鎖,因為在沒有其他 case 准備就緒時,會執行默認情況。我們用默認情況重寫后,程序如下:

package main

import "fmt"

func main() {  
    ch := make(chan string)    
    select {    
        case <-ch:    
        default:
            fmt.Println("default case executed")
    }
}  

以上程序會輸出:

default case executed

如果 select 只含有值為 nil 的信道,也同樣會執行默認情況。

package main

import "fmt"

func main() {  
    var ch chan string
    select {    
        case v := <-ch:
            fmt.Println("received value", v)    
        default:
            fmt.Println("default case executed")

    }
}

在線運行程序

在上面程序中,ch 等於 nil,而我們試圖在 select 中讀取 ch(第 8 行)。如果沒有默認情況,select 會一直阻塞,導致死鎖。由於我們在 select內部加入了默認情況,程序會執行它,並輸出:

default case executed

空 select

package main
func main() {  
    select {}
}

我們已經知道,除非有 case 執行,select 語句就會一直阻塞着。在這里,select 語句沒有任何 case,因此它會一直阻塞,導致死鎖。該程序會觸發 panic,輸出如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:  
main.main()  
    /tmp/sandbox299546399/main.go:4 +0x20


免責聲明!

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



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