Go 学习笔记11 - select 使用 |Go主题月

324 阅读2分钟

select 是操作系统中的系统调用,我们经常会使用 select、poll 和 epoll 等函数构建 I/O 多路复用模型提升程序的性能。在 Go 中当涉及到对多个 channel 进行操作时,通过 select 我们可以同时在多个 channel 上进行发送/接收操作。select 的用法和 switch 比较类似,但是切记 select 的每个 case 是通信(这里的通信指 channel 的接收或者发送)。

一、基本用法

select {
case x := <-c1: // 从channel c1接收数据

case y, ok := <-c2: // 从channel c2接收数据,并根据ok值判断c2是否已经关闭

case c3 <- z: // 将z值发送到channel c3中:

default: // 当上面case中的channel通信均无法实施时,执行该默认分支
}

当程序执行到 select 顶部时,它会评估 case 内部可用于通信的所有 channel,然后阻塞,直到其中一个可以运行为止。一旦其中一个可以运行,对应的 case 就会执行。如果没有 default,select 将永远阻塞直到其中一个 case 的 channel 可以通信。如果有default,且所有 case 中的 channel 都无法通信,则执行default 分支,避免阻塞。

二、select 常见用法:

1.利用 default 分支避免阻塞

当 select 语句中没有可执行的 case 分支时,select 会一直阻塞。此时 select 语句中如果有 default 分支,则可以执行 default 分支语句,从而避免了阻塞。

2.实现超时机制

通过超时事件,我们既可以避免长期陷入某种操作的等待中,也可以做一些异常处理工作。下面示例代码中,如果 <- c 5秒还无法执行,则直接进入超时流程。

func worker() {
	select {
	case <-c:
	     // do something
	case <-time.After(5 *time.Second):
    	fmt.Println("timeout")
	    return
	}
}

3.实现心跳机制

使用 time 包的 Ticker,我们可以实现带有心跳机制的 select。使得我们可以在监听 channel 的同时,执行一些周期性的任务:

func worker() {
	heartbeat := time.NewTicker(1 * time.Second)
	defer heartbeat.Stop()
	for {
		select {
		case <-c:
			// do something
		case <- heartbeat.C:
			// do something
		}
	}
}

4.阻塞 main 函数

func main() {
	bufChan := make(chan int)

	go func() {
		for {
			bufChan <- 1
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for {
			fmt.Println(<-bufChan)
		}
	}()

	select {}
}

上面使用 select ,main 函数就永远阻塞住了。这里要注意上面一定要有一直活动的goroutine否则会报deadlock