go select 使用總結

2023-07-06 06:06:07

轉載請註明出處:

  在Go語言中,select語句用於處理多個通道的並行操作。它類似於switch語句,但是select語句用於通訊操作,而不是條件判斷。select語句會同時監聽多個通道的操作,並選擇其中一個可用的通道進行操作。 select語句的語法如下:

select {
    case <-channel1:
        // 執行channel1的操作
    case data := <-channel2:
        // 執行channel2的操作,同時將通道中的資料賦值給data變數
    case channel3 <- data:
        // 將data寫入channel3
    default:
        // 當沒有任何通道操作時執行default語句
}

  select語句中可以包含多個case子句,每個case子句表示一個通道操作。<-符號用於從通道中讀取資料,channel <- data用於將資料寫入通道。 select語句的執行流程為:

  • 如果有多個通道都可以操作,則隨機選擇一個進行操作。

  • 如果沒有任何通道可以操作,則會執行default語句(如果有)。

  • 如果沒有default語句,則select語句會阻塞,直到至少有一個通道可以操作。

  下面是一個使用select語句的範例:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        time.Sleep(time.Second)
        ch1 <- 1
        fmt.Println("ch1 ending")
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- 2
        fmt.Println("ch2 ending")
    }()

    select {
        case data := <-ch1:
            fmt.Println("收到ch1的資料:", data)
        case data := <-ch2:
            fmt.Println("收到ch2的資料:", data)
        case <-time.After(100 * time.Second):
            fmt.Println("超時:沒有接收到任何資料")
    }
}

  在上面的例子中,我們建立了兩個通道ch1ch2,並分別在不同的goroutine中向它們傳送資料。然後使用select語句監聽這兩個通道的操作,當其中一個通道可用時,就會執行對應的case語句。

  在這個例子中,由於ch1的資料傳送操作會在1秒後執行,而ch2的資料傳送操作會在2秒後執行,因此select語句會等待1秒後,執行ch1case語句,輸出"收到ch1的資料: 1"。如果我們將ch1的傳送操作改為在2秒後執行,那麼select語句將會等待2秒後,執行ch2case語句,輸出"收到ch2的資料: 2"。

  需要注意的是,select語句的執行順序是隨機的,所以不能依賴於某個通道的操作先於其他通道。這也是使用select語句時需要注意的地方之一。

  如果希望持續監聽多個通道的操作,可以將select語句放在一個無限迴圈中。

package main
import (
    "fmt"
    "time"
)
func main() {
    channel1 := make(chan int)
    channel2 := make(chan string)
    go func() {
        for i := 0; i < 5; i++ {
            time.Sleep(time.Second)
            channel1 <- i
        }
        close(channel1)
    }()
    go func() {
        for i := 0; i < 5; i++ {
            time.Sleep(time.Second)
            channel2 <- fmt.Sprintf("Message %d", i)
        }
        close(channel2)
    }()
    for {
        select {
        case data1, ok := <-channel1:
            if ok {
                fmt.Println("Received from channel1:", data1)
            } else {
                fmt.Println("Channel1 closed")
                channel1 = nil
            }
        case data2, ok := <-channel2:
            if ok {
                fmt.Println("Received from channel2:", data2)
            } else {
                fmt.Println("Channel2 closed")
                channel2 = nil
            }
        }
        if channel1 == nil && channel2 == nil {
            break
        }
    }
    fmt.Println("Done")
}

  在這個範例中,建立了兩個通道channel1channel2,分別用於傳送不同型別的資料。然後分別啟動兩個goroutine,每個goroutine向對應的通道傳送一些資料,然後關閉通道。其執行得結果如圖:

 

              

  上述程式碼中 ok 是從通道的屬性中獲取的。在Go語言中,當從通道接收資料時,會返回兩個值:接收到的資料和一個表示通道是否已關閉的布林值。這個布林值就是ok。 當通道已關閉且沒有資料可讀取時,會返回通道元素型別的零值和false。當通道還未關閉且有資料可讀取時,會返回通道中的資料和true 因此,使用data, ok := <-channel的語法可以同時接收通道中的資料和判斷通道是否已關閉。data表示接收到的資料,ok表示通道是否還有資料可讀取。如果okfalse,則表示通道已關閉,沒有資料可讀取。

  在主函數中,我們使用無限迴圈和select語句來持續監聽這兩個通道的操作。每次迴圈時,select語句會選擇其中一個可用的通道進行操作。如果通道關閉,我們會將對應的通道設定為nil,以便在後續的迴圈中跳過該通道的操作。當兩個通道都關閉,即channel1channel2都為nil時,我們跳出迴圈,程式結束。

  執行上述程式碼,你會看到程式持續監聽兩個通道的操作,並列印接收到的資料,直到兩個通道都關閉。最後,程式輸出"Done"並結束。