go中的关键字-select
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") }
- go语言学习第五天==>并发编程,go关键词,并发通信channel、select关键字、缓冲机制、socket编程
- go select用法
- 关于Sql关键字SELECT FROM GROUP ORDER 等的执行顺序问题的解释
- go里面select-case和time.Ticker的使用注意事项
- GO文件名、关键字、标识符
- SQL基础02-select关键字
- mysql 去除重复 Select中DISTINCT关键字的用法
- MySql的select语句的关键字一般顺序
- 【GOLANG】Go语言学习-select用法
- UNION关键字报错:ERROR 1222 (21000): The used SELECT statements have a different number of columns
- 关于Sql关键字SELECT FROM GROUP ORDER 等的执行顺序问题的解释
- ORA-00928: 缺失 SELECT 关键字
- mysql 去除重复 Select中DISTINCT关键字的用法
- SELECT * FROM `magazine` WHERE CONCAT(`title`,`tag`,`description`) LIKE ‘%关键字%’
- 【Go入门教程9】并发(goroutine,channels,Buffered Channels,Range和Close,Select,超时,runtime goroutine)
- Go语言学习笔记 -- 变量、类型和关键字
- 【Go入门教程8】总结(25个关键字)
- GO select用法详解
- Go语言学习笔记 --- 关键字、注释、结构、包相关和可见性规则
- BeautifulSoup中select关键字