go关键字-select 用法

更新时间: 2022-03-04 16:18:10 在golang里头select的功能与epoll(nginx)/poll/select的功能类似,都是坚挺IO操作,当IO操作发生的时候,触发相应的动作。

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")

}

DONE:

for {

select {

ase data := <- ch:

fmt.Println(data)

default:

fmt.Println("通道channel已经空啦!")

break DONE

}

}

}

输出内容为随机一二里面的任意一个。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语句。

package main

import (

"time"

"fmt"

)

func main() {

c1 := make(chan string)

c2 := make(chan string)

go func() {

time.Sleep(time.Second * 1)

c1 <- "one"

}()

go func() {

time.Sleep(time.Second * 2)

c2 <- "two"

45 }()

for i := 0; i < 2; i++ {

select {

case msg1 := <-c1:

fmt.Println("received", msg1)

case msg2 := <-c2:

fmt.Println("received", msg2)

}

}

switch

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

package main

import "fmt"

import "time"

func main() {

i := 2

fmt.Print("Write ", i, " as ")

switch i {

case1:

fmt.Println("one")

case2:

fmt.Println("two")

case3:

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) {

casebool:

fmt.Println("I'm a bool")

caseint:

fmt.Println("I'm an int")

default:

fmt.Printf("Don't know type %T\n", t)

}

}

whatAmI(true)

whatAmI(1)

whatAmI("hey")

}