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 int, 1)
- ch<-1
- 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 bool, 1)
- go func() {
- time.Sleep(1*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:
- case <-time.After(time.Second * 1): // 利用 time 来实现, After 代表多少时间后执行输出东西
- fmt.Println("超时啦!")
- }
- }
判断 channel 是否阻塞 (或者说 channel 是否已经满了)
- package main
- import (
- "fmt"
- )
- func main() {
- ch := make (chan int, 1) // 注意这里给的容量是 1
- ch <- 1
- select {
- case ch <- 2:
- default:
- fmt.Println("通道 channel 已经满啦, 塞不下东西了!")
- }
- }
退出机制
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- i := 0
- ch := make(chan string, 0)
- defer func() {
- close(ch)
- }()
- go func() {
- DONE:
- for {
- time.Sleep(1*time.Second)
- fmt.Println(time.Now().Unix())
- i++
- select {
- case m := <-ch:
- println(m)
- break DONE // 跳出 select 和 for 循环
- default:
- }
- }
- }()
- time.Sleep(time.Second * 4)
- 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[1]; // 每个 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 语句.
- 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 不同.
- package main
- import "fmt"
- import "time"
- func main() {
- i := 2
- fmt.Print("Write", i, "as")
- switch i {
- case 1:
- fmt.Println("one")
- case 2:
- fmt.Println("two")
- case 3:
- 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 {
- case t.Hour() < 12:
- 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(1)
- whatAmI("hey")
- }
来源: https://www.cnblogs.com/33debug/p/11891154.html