select

定义:在golang里头select的功能与epoll(nginx)/poll/select的功能类似,都是坚挺IO操作,当IO操作发生的时候,触发相应的动作

1.1 一些使用规范
  在Go的语言规范中,select中的case的执行顺序是随机的,当有多个case都可以运行,select会随机公平地选出一个执行,其他的便不会执行:



复制代码
1 package main
2
3 import “fmt”
4
5 func main() {
6 ch := make (chan int, 1)
7
8 ch<-1
9 select {
10 case <-ch:
11 fmt.Println(“随机一”)
12 case <-ch:
13 fmt.Println(“随机二n”)
14 }
15 }
复制代码
  输出内容为随机一二里面的任意一个。



  case后面必须是channel操作,否则报错;default子句总是可运行的,所以没有default的select才会阻塞等待事件 ;没有运行的case,那么将会阻塞事件发生报错(死锁)。



1.2 select的应用场景
timeout 机制(超时判断)
复制代码
1 package main
2
3 import (
4 “fmt”
5 “time”
6 )
7
8 func main() {
9 timeout := make (chan bool, 1)
10 go func() {
11 time.Sleep(1*time.Second) // 休眠1s,如果超过1s还没I操作则认为超时,通知select已经超时啦~
12 timeout <- true
13 }()
14 ch := make (chan int)
15 select {
16 case <- ch:
17 case <- timeout:
18 fmt.Println(“超时啦!”)
19 }
20 }
复制代码
  也可以这么写:



复制代码
1 package main
2
3 import (
4 “fmt”
5 “time”
6 )
7
8 func main() {
9 ch := make (chan int)
10 select {
11 case <-ch:
12 case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西
13 fmt.Println(“超时啦!”)
14 }
15 }
复制代码
  判断channel是否阻塞(或者说channel是否已经满了)



复制代码
1 package main
2
3 import (
4 “fmt”
5 )
6
7 func main() {
8 ch := make (chan int, 1) // 注意这里给的容量是1
9 ch <- 1
10 select {
11 case ch <- 2:
12 default:
13 fmt.Println(“通道channel已经满啦,塞不下东西了!”)
14 }
15 }
复制代码
  退出机制



复制代码
1 package main
2
3 import (
4 “fmt”
5 “time”
6 )
7
8 func main() {
9 i := 0
10 ch := make(chan string, 0)
11 defer func() {
12 close(ch)
13 }()
14
15 go func() {
16 DONE:
17 for {
18 time.Sleep(1*time.Second)
19 fmt.Println(time.Now().Unix())
20 i++
21
22 select {
23 case m := <-ch:
24 println(m)
25 break DONE // 跳出 select 和 for 循环
26 default:
27 }
28 }
29 }()
30
31 time.Sleep(time.Second * 4)
32 ch<-“stop”
33 }
复制代码



  1. select的实现
      select-case中的chan操作编译成了if-else。如:



1 select {
2 case v = <-c:
3 …foo
4 default:
5 …bar
6 }
  会被编译为:



1 if selectnbrecv(&v, c) {
2 …foo
3 } else {
4 …bar
5 }
  类似地



1 select {
2 case v, ok = <-c:
3 … foo
4 default:
5 … bar
6 }
  会被编译为:



1 if c != nil && selectnbrecv2(&v, &ok, c) {
2 … foo
3 } else {
4 … bar
5 }
  selectnbrecv函数只是简单地调用runtime.chanrecv函数,不过是设置了一个参数,告诉当runtime.chanrecv函数,当不能完成操作时不要阻塞,而是返回失败。也就是说,所有的select操作其实都仅仅是被换成了if-else判断,底层调用的不阻塞的通道操作函数。



  在Go的语言规范中,select中的case的执行顺序是随机的,那么,如何实现随机呢?



  select和case关键字使用了下面的结构体:



复制代码
1 struct Scase
2 {
3 SudoG sg; // must be first member (cast to Scase)
4 Hchan* chan; // chan
5 byte* pc; // return pc
6 uint16 kind;
7 uint16 so; // vararg of selected bool
8 bool* receivedp; // pointer to received bool (recv2)
9 };
复制代码
复制代码
1 struct Select
2 {
3 uint16 tcase; // 总的scase[]数量
4 uint16 ncase; // 当前填充了的scase[]数量
5 uint16* pollorder; // case的poll次序
6 Hchan** lockorder; // channel的锁住的次序
7 Scase scase[1]; // 每个case会在结构体里有一个Scase,顺序是按出现的次序
8 };
复制代码
  每个select都对应一个Select结构体。在Select数据结构中有个Scase数组,记录下了每一个case,而Scase中包含了Hchan。然后pollorder数组将元素随机排列,这样就可以将Scase乱序了。




  1. select死锁
      select不注意也会发生死锁,分两种情况:



  如果没有数据需要发送,select中又存在接收通道数据的语句,那么将发送死锁



复制代码
1 package main
2 func main() {

3 ch := make(chan string)
4 select {
5 case <-ch:
6 }
7 }
复制代码
  预防的话加default。



  空select,也会引起死锁。



1 package main
2
3 func main() {

4 select {}
5 }



  1. select和switch的区别
    select
    select只能应用于channel的操作,既可以用于channel的数据接收,也可以用于channel的数据发送。如果select的多个分支都满足条件,则会随机的选取其中一个满足条件的分支, 如规范中所述:
    If multiple cases can proceed, a uniform pseudo-random choice is made to decide which single communication will execute.
    `case`语句的表达式可以为一个变量或者两个变量赋值。有default语句。
    复制代码
    31 package main 32 import “time”
    33 import “fmt”

    35 func main() { 36 c1 := make(chan string)
    37 c2 := make(chan string) 38 go func() {
    39 time.Sleep(time.Second * 1) 40 c1 <- “one”
    41 }() 42 go func() {
    43 time.Sleep(time.Second * 2) 44 c2 <- “two”
    45 }() 46 for i := 0; i < 2; i++ {
    47 select { 48 case msg1 := <-c1:
    49 fmt.Println(“received”, msg1)

    50 case msg2 := <-c2:
    51 fmt.Println(“received”, msg2)

    52 }
    53 }
    复制代码
    switch
      switch可以为各种类型进行分支操作, 设置可以为接口类型进行分支判断(通过i.(type))。switch 分支是顺序执行的,这和select不同。
    复制代码
    1 package main

    2 import “fmt”
    3 import “time”

    4
    5 func main() {

    6 i := 2
    7 fmt.Print(“Write “, i, “ as “)

    8 switch i {
    9 case 1:
    10 fmt.Println(“one”)
    11 case 2:

    12 fmt.Println(“two”)
    13 case 3:

    14 fmt.Println(“three”)
    15 }

    16 switch time.Now().Weekday() {
    17 case time.Saturday, time.Sunday:
    18 fmt.Println(“It’s the weekend”)
    19 default:

    20 fmt.Println(“It’s a weekday”)
    21 }

    22 t := time.Now()
    23 switch {

    24 case t.Hour() < 12:
    25 fmt.Println(“It’s before noon”)

    26 default:
    27 fmt.Println(“It’s after noon”)

    28 }
    29 whatAmI := func(i interface{}) {

    30 switch t := i.(type) {
    31 case bool:

    32 fmt.Println(“I’m a bool”)
    33 case int:

    34 fmt.Println(“I’m an int”)
    35 default:

    36 fmt.Printf(“Don’t know type %T\n”, t)
    37 }
    38 }
    39 whatAmI(true)

    40 whatAmI(1)
    41 whatAmI(“hey”)

    42 }


Category golang