Go 快速入門指南 - select

洋芋土豆發表於2022-12-25

概述

select 類似 switch, 包含一系列邏輯分支和一個可選的預設分支。每一個分支對應通道上的一次操作 (傳送或接收),
可以將 select 理解為專門針對通道操作的 switch 語句

語法規則

select {
case v1 := <- ch1:
// do something ...  
case v2 := <- ch2:
// do something ...
default:
// do something ...
}

執行順序

  • 當同時存在多個滿足條件的通道時,隨機選擇一個執行
  • 如果沒有滿足條件的通道時,檢測是否存在 default 分支

    • 如果存在則執行
    • 否則阻塞等待

通常情況下,把含有 default 分支select 操作稱為 無阻塞通道操作

例子

隨機執行一個

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    done := make(chan bool)

    go func() {
        ch1 <- "hello"
    }()

    go func() {
        ch2 <- "world"
    }()

    go func() {
        done <- true
    }()

    time.Sleep(time.Second) //  休眠 1 秒

    // 此時 3 個通道應該都滿足條件,select 會隨機選擇一個執行
    select {
    case msg := <-ch1:
        fmt.Printf("ch1 msg = %s\n", msg)
    case msg := <-ch2:
        fmt.Printf("ch2 msg = %s\n", msg)
    case <-done:
        fmt.Println("done !")
    }

    close(ch1)
    close(ch2)
    close(done)
}

// $ go run main.go
// 輸出如下,你的輸出可能和這裡的不一樣, 多執行幾次看看效果
/**
  ch1 msg = hello
*/

default (無阻塞通道操作)

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    done := make(chan bool)

    go func() {
        time.Sleep(time.Second)
        ch1 <- "hello"
    }()

    go func() {
        time.Sleep(time.Second)
        ch2 <- "world"
    }()

    go func() {
        time.Sleep(time.Second)
        done <- true
    }()

    // 此時 3 個通道都在休眠中, 不滿足條件,select 會執行 default 分支
    select {
    case msg := <-ch1:
        fmt.Printf("ch1 msg = %s\n", msg)
    case msg := <-ch2:
        fmt.Printf("ch2 msg = %s\n", msg)
    case <-done:
        fmt.Println("done !")
    default:
        fmt.Println("default !")
    }

    close(ch1)
    close(ch2)
    close(done)
}

// $ go run main.go
// 輸出如下
/**
  default !
*/

和 for 搭配使用

透過在 select 外層加一個 for 迴圈,可以達到 無限輪詢 的效果。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    done := make(chan bool)

    go func() {
        // ch1 goroutine 輸出 1 次 
        fmt.Println("[ch1 goroutine]")
        time.Sleep(time.Second)
        ch1 <- "hello"
    }()

    go func() {
        // ch2 goroutine 輸出 2 次
        for i := 0; i < 2; i++ {
            fmt.Println("[ch2 goroutine]")
            time.Sleep(time.Second)
        }
        ch2 <- "world"
    }()

    go func() {
        // done goroutine 輸出 3 次
        for i := 0; i < 3; i++ {
            fmt.Println("[done goroutine]")
            time.Sleep(time.Second)
        }
        done <- true
    }()

    for exit := true; exit; {
        select {
        case msg := <-ch1:
            fmt.Printf("ch1 msg = %s\n", msg)
        case msg := <-ch2:
            fmt.Printf("ch2 msg = %s\n", msg)
        case <-done:
            fmt.Println("done !")
            exit = false // 透過變數控制外層 for 迴圈退出
        }
    }

    close(ch1)
    close(ch2)
    close(done)
}

// $ go run main.go
// 輸出如下,你的輸出順序可能和這裡的不一樣
/**
  [done goroutine]
  [ch2 goroutine]
  [ch1 goroutine]
  ch1 msg = hello
  [done goroutine]
  [ch2 goroutine]
  ch2 msg = world
  [done goroutine]
  done !
*/

從輸出結果看,[ch1 goroutine] 輸出了 1 次,[ch2 goroutine] 輸出了 2 次,[done goroutine] 輸出了 3 次。

聯絡我

公眾號

相關文章