姊妹篇:
golang之channel入门
golang之channel并发访问
Select vs Switch
二者有个共同特性就是都通过case的方式来处理, 但除此之外几乎完全不同;
switch..case 可以处理各种类型,常用来做 接口 interface{} 的判断 (通过variable.(type)). 重点是会依照 case 的顺序依序执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" func convert (val interface {}) { switch t := val.(type ) { case int : fmt.Println("val为int类型" , t) case string : fmt.Println("val为string类型" , t) case float64 : fmt.Println("val为float64类型" , t) case float32 : fmt.Println("val为float32类型" , t) case []string : fmt.Println("val为字符串类型的切片" , t) default : fmt.Println("val不是上列类型之一" ) } } func main () { var i interface {} i = float32 (3.1415926 ) convert(i) i = "dashen" convert(i) i = 100 convert(i) i = []string {"欧拉" , "高斯" } convert(i) }
输出为:
1 2 3 4 val为float32 类型 3.1415925 val为string 类型 dashen val为int 类型 100 val为字符串类型的切片 [欧拉 高斯]
而select..case则只能处理 channel类型
即每个 case 必须是一个通信操作, 要么是发送要么是接收
select 将随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。
Select的四大基本用法
随机选择
Random Select
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" func main () { var ch1, ch2, ch3 chan int var i1, i2 int select { case i1 = <-ch1: fmt.Println("接收到了管道1的一条数据:" , i1) case ch2 <- i2: fmt.Println("向管道2发送了一条数据:" , i2) case i3, ok := <-ch3: if ok { fmt.Println("收到了管道3的数据:" , i3) } else { fmt.Println("管道3已被关闭" ) } default : fmt.Println("以上case皆不可运行,即没有进行通信" ) } }
输出为:
如果把上述代码中的default语句块去掉,则会报
1 2 3 4 5 6 7 fatal error : all goroutines are asleep - deadlock! goroutine 1 [select ]: main.main() /Users/shuangcui/go /note/select /2. go :9 +0x108 exit status 2
而如果将如上代码中的channel改成有缓存,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport "fmt" func main () { ch1 := make (chan int ,1 ) ch2 := make (chan int ,1 ) ch3 := make (chan int ,1 ) ch6 := make (chan int ,1 ) var i1, i2 int select { case i1 = <-ch1: fmt.Println("接收到了管道1的一条数据:" , i1) case ch2 <- i2: fmt.Println("向管道2发送了一条数据:" , i2) case i3, ok := <-ch3: if ok { fmt.Println("收到了管道3的数据:" , i3) } else { fmt.Println("管道3已被关闭" ) } case ch6 <- 271828 : fmt.Println("向管道6发送了一条数据:" , 271828 ) default : fmt.Println("以上case皆不可运行,即没有进行通信" ) } }
第二个case和第四个case是可以执行的,所以不会走default语句.输出为:
或
二者都满足条件,被select选中执行的概率完全一样
超时控制
假设业务中需调用某接口A,要求超时时间为x秒,那么如何优雅、简洁的实现呢?
利用 select+time.After
参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "time" ) func main () { ch := make (chan string ) go func () { time.Sleep(time.Second * 2 ) ch <- "写入某个值" }() select { case rs := <-ch: fmt.Println("结果是:" , rs) case <-time.After(time.Second * 1 ): fmt.Println("超时!" ) } }
输出为:
参考:
Go 采用 time.After 实现超时控制
如何用 go 实现超时控制
Go语言并发模型:使用 select
关于time.After:
1 2 3 4 5 6 7 8 9 func After (d Duration) <-chan Time { return NewTimer(d).C }
接收一个int64类型的值,返回一个Time类型的单向channel
检查channel是否已满
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func main () { ch := make (chan int , 1 ) ch <- 271828 select { case ch <- 31415926 : fmt.Println("通道的值为:" , <-ch) fmt.Println("channel vaule is:" ,<-ch) default : fmt.Println("通道已经被阻塞,即已经满了" ) } }
输出为:
将如上ch这个int类型通道的缓存值从1改称10,则
1 ch := make (chan int , 10 )
则执行结果为:
1 2 通道的值为: 271828 channel vaule is: 31415926
在循环中使用select
如果有多个 channel 需要读取, 且读取是不间断的, 就必须使用 for + select 机制来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "time" ) func main () { i := 0 ch := make (chan string , 0 ) defer func () { close (ch) }() go func () { CuiStartLoop: for { time.Sleep(time.Second * 1 ) fmt.Println(time.Now().Unix()) fmt.Println("当前i的值为:" ,i) i++ select { case m := <-ch: fmt.Println("输出为:" ,m) break CuiStartLoop default : fmt.Println("执行了default代码块" ) } } }() time.Sleep(time.Second * 4 ) ch <- "stop" }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 1584289065 当前i的值为: 0 执行了default 代码块 1584289066 当前i的值为: 1 执行了default 代码块 1584289067 当前i的值为: 2 执行了default 代码块 1584289068 当前i的值为: 3 输出为: stop
当没有值传送进来时, 就会一直停在 select 区段, 所以即便没有 default代码块 也是可以正常运作的.
而要结束 for 或 select 都需要通过 break 来结束, 但是要在 select 区间直接结束掉 for 循环, 只能使用 break.
参考:
一文掌握 Go 语言 Select 的四大用法
更底层(操作系统层面)地进行理解:
select实现原理
Go netpoll I/O 多路复用构建原生网络模型之源码深度解析
一个EOF引发的探索之路之四(理解golang的NetFD之I/O多路复用篇)
原文链接: https://dashen.tech/2020/03/15/Golang中select的四大用法/
版权声明: 转载请注明出处.