首頁 > 軟體

Go select使用與底層原理講解

2022-07-31 18:01:27

1. select的使用

select 是 Go 提供的 IO 多路複用機制,可以用多個 case 同時監聽多個 channl 的讀寫狀態:

  • case: 可以監聽 channl 的讀寫訊號
  • default:宣告預設操作,有該欄位的 select 不會阻塞
select {
case chan <-:
    // TODO
case <- chan:
    // TODO
default:
    // TODO
}

2. 底層原理

  • 每一個 case 對應的 channl 都會被封裝到一個結構體中;
  • 當第一次執行到 select 時,會鎖住所有的 channl 並且,打亂 case 結構體的順序;
  • 按照打亂的順序遍歷,如果有就緒的訊號,就直接走對應 case 的程式碼段,之後跳出 select;
  • 如果沒有就緒的程式碼段,但是有 default 欄位,那就走 default 的程式碼段,之後跳出 select;
  • 如果沒有 default,那就將當前 goroutine 加入所有 channl 的對應等待佇列;
  • 當某一個等待佇列就緒時,再次鎖住所有的 channl,遍歷一遍,將所有等待佇列中的 goroutine 取出,之後執行就緒的程式碼段,跳出select。

3. 資料結構

每一個 case 對應的資料結構如下:

type scase struct {
    c           *hchan         // chan
    elem        unsafe.Pointer // 讀或者寫的緩衝區地址
    kind        uint16   //case語句的型別,是default、傳值寫資料(channel <-) 還是  取值讀資料(<- channel)
    pc          uintptr // race pc (for race detector / msan)
    releasetime int64
}

4. 幾種常見 case

學習了 select 的使用與原理,我們就能更輕鬆地分辨不同情況下的輸出情況了。

case 1

package main

import (
  "fmt"
  "time"
)

func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  go func() {
    chan1 <- 1
    time.Sleep(5 * time.Second)
  }()
  go func() {
    chan2 <- 1
    time.Sleep(5 * time.Second)
  }()
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
    default:
      fmt.Println("default")
  }
}

三種輸出都有可能。

case2

package main

import (
  "fmt"
  "time"
)
func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
  }
  fmt.Println("main exit.")
}

上述程式會一直阻塞。

case3

package main

import (
  "fmt"
)

func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  
  go func() {
    close(chan1)
  }()
  go func() {
    close(chan2)
  }()
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
  }
  fmt.Println("main exit.")
}

隨機執行1或者2.

case4

package main

func main() {
  select {
  }
}

對於空的 select 語句,程式會被阻塞,確切的說是當前協程被阻塞,同時 Go 自帶死鎖檢測機制,當發現當前協程再也沒有機會被喚醒時,則會發生 panic。所以上述程式會 panic。

到此這篇關於Go select使用與底層原理講解的文章就介紹到這了,更多相關Go select使用 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com