go中的关键字-select

1. 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 }

2. 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乱序了。

 3. 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 }

 4. 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  }

 

posted @ 2019-11-20 17:03  滴巴戈  阅读(1845)  评论(0编辑  收藏  举报