go中的关键字-select

2019-11-20 来源: 滴巴戈 发布在  https://www.cnblogs.com/33debug/p/11891154.html

1. select的使用

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

1.1 一些使用规范

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

 package main

 import "fmt"

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

     ch<-
     select {
     case <-ch:
         fmt.Println("随机一")
     case <-ch:
         fmt.Println("随机二n")
     }
 }

  输出内容为随机一二里面的任意一个。

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

1.2 select的应用场景

timeout 机制(超时判断)
 package main

 import (
     "fmt"
     "time"
 )

 func main() {
     timeout := make (chan )
     go func() {
         time.Sleep(*time.Second) // 休眠1s,如果超过1s还没I操作则认为超时,通知select已经超时啦~
         timeout <- true
     }()
     ch := make (chan int)
     select {
     case <- ch:
     case <- timeout:
         fmt.Println("超时啦!")
     }
 }

  也可以这么写:

 package main

 import (
     "fmt"
     "time"
 )

 func main() {
     ch := make (chan int)
     select {
     case <-ch:
     ): // 利用time来实现,After代表多少时间后执行输出东西
         fmt.Println("超时啦!")
     }
 }

  判断channel是否阻塞(或者说channel是否已经满了)

 package main

 import (
     "fmt"
 )

 func main() {
     ch := make (chan )  // 注意这里给的容量是1
     ch <-
     select {
     :
     default:
         fmt.Println("通道channel已经满啦,塞不下东西了!")
     }
 }

  退出机制

 package main

 import (
     "fmt"
     "time"
 )

 func main() {
     i :=
     ch := make(chan )
     defer func() {
         close(ch)
     }()

     go func() {
         DONE:
         for {
             time.Sleep(*time.Second)
             fmt.Println(time.Now().Unix())
             i++

             select {
             case m := <-ch:
                 println(m)
                 break DONE // 跳出 select 和 for 循环
             default:
             }
         }
     }()

     time.Sleep(time.Second * )
     ch<-"stop"
 }

2. select的实现

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

  select {
  case v = <-c:
          ...foo
  default:
          ...bar
  }

  会被编译为:

  if selectnbrecv(&v, c) {
          ...foo
  } else {
          ...bar
  }

  类似地

  select {
  case v, ok = <-c:
      ... foo
  default:
      ... bar
  }

  会被编译为:

  if c != nil && selectnbrecv2(&v, &ok, c) {
      ... foo
  } else {
      ... bar
  }

  selectnbrecv函数只是简单地调用runtime.chanrecv函数,不过是设置了一个参数,告诉当runtime.chanrecv函数,当不能完成操作时不要阻塞,而是返回失败。也就是说,所有的select操作其实都仅仅是被换成了if-else判断,底层调用的不阻塞的通道操作函数。

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

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

 struct    Scase
   {
       SudoG    sg;            // must be first member (cast to Scase)
       Hchan*    chan;        // chan
       byte*    pc;            // return pc
       uint16    kind;
       uint16    so;            // vararg of selected bool
       bool*    receivedp;    // pointer to received bool (recv2)
   };
  struct    Select
      {
      uint16    tcase;            // 总的scase[]数量
      uint16    ncase;            // 当前填充了的scase[]数量
      uint16*    pollorder;        // case的poll次序
      Hchan**    lockorder;        // channel的锁住的次序
      Scase    scase[];        // 每个case会在结构体里有一个Scase,顺序是按出现的次序
  };

  每个select都对应一个Select结构体。在Select数据结构中有个Scase数组,记录下了每一个case,而Scase中包含了Hchan。然后pollorder数组将元素随机排列,这样就可以将Scase乱序了。

3. select死锁

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

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

 package main
 func main() {
     ch := make(chan string)
     select {
     case <-ch:
     }
 }

  预防的话加default。

  空select,也会引起死锁。

 package main

 func main() {
     select {}
 }

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语句。
 package main                                                                                                                                               import "time"
 import "fmt"
 func main() {                                                                                                                                                  c1 := make(chan string)
     c2 := make(chan      go func() {
         time.Sleep(time.Second * )                                                                                                                                c1 <- "one"
     }()                                                                                                                                                        go func() {
         time.Sleep(time.Second * )                                                                                                                                c2 <- "two"
     }()                                                                                                                                                        ; i < ; i++ {
                      case msg1 := <-c1:
             fmt.Println(             case msg2 := <-c2:
             fmt.Println(         }
  }

switch

  switch可以为各种类型进行分支操作, 设置可以为接口类型进行分支判断(通过i.(type))。switch 分支是顺序执行的,这和select不同。
 package main
 import "fmt"
 import "time"  

 func main() {
      i :=
      fmt.Print("Write ", i, " as ")
      switch i {
          :
          fmt.Println("one")
          :
          fmt.Println("two")
          :
          fmt.Println("three")
      }
      switch time.Now().Weekday() {
          case time.Saturday, time.Sunday:
          fmt.Println("It's the weekend")
          default:
          fmt.Println("It's a weekday")
      }
      t := time.Now()
      switch {
          :
          fmt.Println("It's before noon")
          default:
          fmt.Println("It's after noon")
      }
      whatAmI := func(i interface{}) {
          switch t := i.(type) {
              case bool:
              fmt.Println("I'm a bool")
              case int:
              fmt.Println("I'm an int")
              default:
              fmt.Printf("Don't know type %T\n", t)
          }
      }
      whatAmI(true)
      whatAmI()
      whatAmI("hey")
  }

相关文章