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
If multiple cases can proceed, a uniform pseudo-random choice is made to decide which single communication will execute.
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
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 }