Golang select 基础语法与用法

Golang select 基础语法与用法

select语句用来处理与channel有关的I/O操作。

语法:

  1. 每个case都必须是一个通信;
  2. 所有channel表达式和被发送的表达式都会被求值;
  3. 任意某个通道可以运行,它就执行,其他被忽略;
  4. 多个case可以运行,随机选一个执行;
  5. 都不可以运行,有default,执行default,没有就阻塞,直到某个通信可以运行,且不会重新对表达式求值;
  6. 一个select最多执行一次case里的代码,需要一直检测case,外层加for循环;
  7. case里的break只退出当前select,和for循环无关;

示例一,执行default:

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

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

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

   //time.Sleep(2*time.Second)

   select {
   case i := <-ch1:
      fmt.Println("ch1 receive:", i)
   case i := <-ch2:
      fmt.Println("ch2 receive:", i)
   default:
      fmt.Println("no i/o operation")
   }
}

结果总是打印:no i/o operation,因为当主线程走到select时,两个channel所在goroutine里的channel还没有等待写入,输出就会阻塞,执行default。

示例二,随机执行case:

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

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

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

   time.Sleep(2 * time.Second)

   select {
   case i := <-ch1:
      fmt.Println("ch1 receive:", i)
   case i := <-ch2:
      fmt.Println("ch2 receive:", i)
   default:
      fmt.Println("no i/o operation")
   }
}

结果随机打印:ch1 receive: 1ch2 receive: 2,因为两个channal等待写入,select里两个case都符合执行条件,随机执行。

示例三,所有表达式都会被求值:

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

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

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

   select {
   case getChannel(1, ch1) <- getValue(1):
      fmt.Println("send 1 to ch1")
   case getChannel(2, ch2) <- getValue(2):
      fmt.Println("send 2 to ch2")
   default:
      fmt.Println("no i/o operation")
   }
}

func getValue(i int) int {
   fmt.Println("getValue:", i)
   return i
}

func getChannel(i int, ch chan int) chan int {
   fmt.Println("getChannel:", i)
   return ch
}

结果打印如下,case所有表达式从左到右,从上到下,依次求值:

getChannel: 1
getValue: 1
getChannel: 2
getValue: 2
no i/o operation

用法:

示例一,当有goroutine运行时,用select{} 简单阻塞一下主流程:

func main() {
   go func() {
      for {
         time.Sleep(5 * time.Second)
         fmt.Println(1)
      }
   }()
   
   select {}
}

示例二,实现对 channel 操作的超时设置:

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

   go func() {
      time.Sleep(5 * time.Second)
      ch2 <- 1
   }()

   select {
   case i:= <-ch1:
      fmt.Println("ch1 receive:", i)
   case <-ch2:
      fmt.Println("ch1 receive timeout")
   }
}

func main() {
   ch := make(chan int)

   select {
   case i := <-ch:
      fmt.Println("ch receive:", i)
   case <-time.After(5 * time.Second):
      fmt.Println("ch receive timeout")
   }
}

示例三,检查 channel 是否已满阻塞:

func main() {
   ch := make(chan int, 5)
   ch <- 1
   ch <- 2
   ch <- 3
   ch <- 4
   ch <- 5

   select {
   case ch <- 6:
      fmt.Println("send 6 to ch")
   default:
      fmt.Println("channel is full")
   }
}

示例四,退出函数:

func main() {
   stop := make(chan bool, 1)
   stop <- true
      
   select {
   case i := <-stop:
      if i {
         fmt.Println("已退出")
         return
      }
   default:
      fmt.Println("no i/o operation")
   }

   fmt.Println("执行结束")
}

公众号:李田路口