golang關鍵字select的三個例子, time.After模擬socket/心跳超時

Silenzio發表於2020-05-07

golang關鍵字select的三個例子, time.After模擬socket/心跳超時

 

例子1 select會隨機選擇一個可執行的case

 

// 這個例子主要說明select是隨機選擇一個可執行的case
func main() {
  // 定義兩個chan, 用於儲存資料
  // 由於我打算提前把資料存進去, 所以定義緩衝區為10
  chan1 := make(chan int, 10)
  chan2 := make(chan int, 10)
  // 在兩個chan中都放入10個資料
  for i := 0; i < 10; i++ {
    chan1 <- i
  }
  for i := 0; i < 10; i++ {
    chan2 <- i
  }
  // 啟動一個for迴圈, 無限迴圈
  // select的作用:
  // select會隨機選擇一個case, 判斷該case後面的語句是否可以執行
  //     如果可以執行, 進入case的程式碼塊中, 執行程式碼塊
  //     如果不能只能, 則隨機挑選另一個case來進行判斷
  // select會持續這個隨機挑選判斷的過程, 直到某一個case可以被執行, 並且執行它
  // 也就是說select會阻塞在這裡, 直到選擇了某一個case

  // 特別說明: 判斷的順序是隨機的, 看看第一個, 看看第二個, 再看看第二個, 再看看第一個
  // 這個和switch-case是不一樣的
  fmt.Println("即將進入select")
  fmt.Println("你會發現chan 1和chan 2是隨機列印的, 沒有規  ")
  fmt.Println("驗證了select是隨機選擇")
  for {
    select {
    case num1 := <-chan1:
      {
        fmt.Printf("從chan 1中取出了資料, 這是第%d次選擇case 1\n", num1)
      }
    case num2 := <-chan2:
      {
        fmt.Printf("從chan 2中取出了資料, 這是第%d次選擇case 2\n", num2)
      }
    case <-time.After(time.Second * 5):
      {
        // 請忽略這個case, 僅僅是為了退出程式
        // time.After的用法將在case 2中講解
        fmt.Println("5秒了, 程式退出")
        return
      }
    }
  }
}

例子1

 

例子2 select會阻塞, 直到某一個case可以執行 && time.After的用法

 

// 這個例子主要驗證select會阻塞, 直到某一個case可以執行
// 同時也會說明time.After的用法
func main() {
  // 建立一個通道, 在for迴圈中會從中取值
  dataChan := make(chan int)

  // 啟動一個go程
  go func() {
    // 13秒之後, 我往這個dataChan中放入一個資料
    // 這個資料是什麼不重要, 重要的是我放入了一個資料
    time.Sleep(time.Second * 13)
    fmt.Println("13秒到了, 往dataChan放入一個資料")
    dataChan <- 1
  }()

  // 這個go程只是用來列印時間和退出程式的, 對測試內容沒有任何影響
  go func() {
    for i := 0; ; i++ {
      fmt.Printf("%d秒\n", i+1)
      time.Sleep(time.Second * 1)
      if i == 22 {
        fmt.Println("測試結束, 程式退出")
        os.Exit(0)
      }
    }
  }()

  // 啟動一個for迴圈, 無限迴圈
  for {
    // 每次迴圈從這裡開始
    fmt.Println("-----我是select, 我開始阻塞選擇啦-----")
    select {
    case <-dataChan:
      {
        // 判斷是否能從dataChan這個channel中取出資料來
        // 顯然前13秒是取不出來的, 因為沒有人給他放資料
        fmt.Println("13秒到了, 從dataChan中取出了資料")
        fmt.Println("注意觀察, 下一次select的時候, time.After會重新計時5秒")
      }
    case <-time.After(time.Second * 5):
      // time.After的作用:
      // 每次執行到select時
      // 這個time.After會開始計時
      // (如果到了)5秒後, 就會生產出一個資料, select就會認為這個case可以執行, 選擇這個case
      // 如果沒有到5秒, select就選擇了其他case, 那麼這個計時器會清零
      // 下一次迴圈中, 會重新進行5秒計時
      fmt.Println("5秒過去啦, time.After生產出了資料, select選擇了第二個case")
    }
  }
}

例子2

 

例子3 用select來判斷socket/心跳超時

 

// 這個例子是用select來判斷socket/心跳超時
func main() {
  // 用這個channel來模擬socket輸入, 輸入資料或者心跳
  socketChan := make(chan int)
  go func() {
    // 模擬輸入/心跳, 每5秒輸入一個資料/接收到心跳
    for i := 0; i < 2; i++ {
      time.Sleep(time.Second * 5)
      fmt.Printf("socket: 第%d個5秒到了, 我輸入一個資料\n", i+1)
      socketChan <- i
    }
    // 10秒後, socket崩潰, 不再輸入資料/不再傳送心跳
    time.Sleep(time.Millisecond * 1)
    fmt.Println("socket: 我崩潰啦, 沒有辦法繼續輸入資料了")
    fmt.Println("開始計算超時時間, 預計10秒後超時")
  }()

  // 這個go程只是用來列印時間和退出程式的, 對測試內容沒有任何影響
  go func() {
    for i := 0; ; i++ {
      fmt.Printf("%d秒\n", i+1)
      time.Sleep(time.Second * 1)
      if i == 21 {
        fmt.Println("測試結束, 程式退出")
        os.Exit(0)
      }
    }
  }()

  // 啟動一個for迴圈, 無限迴圈
  for {
    fmt.Println("-----我是select, 我開始阻塞選擇啦-----")
    select {
    case num := <-socketChan:
      {
        fmt.Printf("我是select: 我收到了socket輸入 是第%d次\n", num+1)
      }
    case <-time.After(time.Second * 10):
      {
        // 超時時間設定為10秒
        fmt.Println("我是select: 連續10秒都沒有收到socket輸入, 超時了")
        return
      }
    }
  }
}

例子3

相關文章