Go语言学习之 Select

108 阅读2分钟

前端尝试转GO学习(第八天)

select语句用来选择哪个case中的发送或接收操作可以被立即执行。

类似于switch语句,但是它的case涉及到channel有关的I/O操作。

即select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。

  • select 语句会一直阻塞,直到发送/接收操作准备就绪。
  • 如果有多个通道操作准备完毕, select 会随机地选取其中之一执行。
  • 否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。

select 语法如下:

select {
    case expression1:
        code
    case expression2:
        code
    default:
        code
}
  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以执行,它就会执行;其他就会被忽略
  • 如果有多个 case 都可以运行,select 会随机公平的选出一个执行。其他不会执行。
func main() {
   // 创建通道
   ch1 := make(chan string, 1)
   ch2 := make(chan string, 1)
   ch3 := make(chan string, 1)

   // 发送数据
   ch1 <- "React"
   ch2 <- "Vue"
   ch3 <- "Go"

   // 接收数据
   select {
   case message1 := <-ch1:
      fmt.Println("ch1: ", message1)
   case message2 := <-ch2:
      fmt.Println("ch2: ", message2)
   case message3 := <-ch3:
      fmt.Println("ch3: ", message3)
   default:
      fmt.Println(" no")
   }
}

创建了 3 个通道,往3个通道分别发送数据。只要其中一个通道接收到数据,那么就会执行对应的 case 代码。

ch1:  React
ch3:  Vue
ch3:  Go

阻塞

func main() {
   // 创建通道
   ch1 := make(chan string, 1)
   ch2 := make(chan string, 1)
   ch3 := make(chan string, 1)

   // 接收数据
   select {
   case message1 := <-ch1:
      fmt.Println("ch1: ", message1)
   case message2 := <-ch2:
      fmt.Println("ch2: ", message2)
   case message3 := <-ch3:
      fmt.Println("ch3: ", message3)
   }
}

会造成死锁,fatal error: all goroutines are asleep - deadlock!

select 的应用

每个任务执行的时间不同,使用 select 语句等待相应的通道发出响应。

select 会选择首先响应先完成的 task,忽略其它。

这种情况下可以做多个 task,并给用户返回最快的 task 结果。

(有点类似于 JS 中的 Promise.race)

func task1(ch chan string) {
   time.Sleep(1 * time.Second)
   ch <- "task 1"
}

func task2(ch chan string) {
   time.Sleep(2 * time.Second)
   ch <- "task 2"
}

func task3(ch chan string) {
   time.Sleep(3 * time.Second)
   ch <- "task 3"
}

func main() {
   ch1 := make(chan string)
   ch2 := make(chan string)
   ch3 := make(chan string)
   go task1(ch1)
   go task2(ch2)
   go task3(ch3)

   select {
   case message1 := <-ch1:
      fmt.Println("ch1:", message1)
   case message2 := <-ch2:
      fmt.Println("ch2:", message2)
   case message3 := <-ch3:
      fmt.Println("ch3:", message3)
   }
}

Fibonacci 数列

package main
import "fmt"

func fibonacci(c, quit chan int) {
   x, y := 0, 1
   for {
      select {
      case c <- x:
         x, y = y, x+y
      case <-quit:
         fmt.Println("quit")
         return
      }
   }
}
func main() {
   c := make(chan int)
   quit := make(chan int)
   go func() {
      for i := 0; i < 10; i++ {
         fmt.Println(<-c)
      }
      quit <- 0
   }()
   fibonacci(c, quit)
}

本文正在参加技术专题18期-聊聊Go语言框架