Go从入门到放弃18--select多路复用

168 阅读2分钟

select 是操作系统中的系统调用,我们经常会使用 select、poll 和 epoll 等函数构建 I/O 多路复用模型提升程序的性能。Go 语言的 select 与操作系统中的 select 比较相似,Go 语言中的 select 也能够让 Goroutine 同时等待多个 Channel 可读或者可写,在多个文件或者 Channel状态改变之前,select 会一直阻塞当前线程或者 Goroutine。

  select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    }

select随机选择机制

select 在遇到多个 <-ch 同时满足可读或者可写条件时会随机选择一个 case 执行其中的代码。

func main() {
	ch := make(chan int)
	go func() {
		for range time.Tick(1 * time.Second) {
			ch <- 0
		}
	}()

	for {
		select {
		case <-ch:
			println("case1")
		case <-ch:
			println("case2")
		}
	}
}

$ go run main.go
case1
case2
case1
...

非阻塞的收发

在通常情况下,select语句会阻塞当前 Goroutine 并等待多个 Channel 中的一个达到可以收发的状态。但是如果 select 控制结构中包含 default 语句,那么这个 elect语句在执行时会遇到以下两种情况:

  • 当存在可以收发的 Channel 时,直接处理该 Channel 对应的 case;
  • 当不存在可以收发的 Channel 时,执行 default中的语句;

当我们运行下面的代码时就不会阻塞当前的 Goroutine,它会直接执行 default 中的代码。

func main() {
	ch := make(chan int)
	select {
	case i := <-ch:
		println(i)

	default:
		println("default")
	}
}

$ go run main.go
default

select超时控制

带超时机制的 select,是 Go 中常见的一种 select 和 channel 的组合用法。通过超时事件,我们既可以避免长期陷入某种操作的等待中,也可以做一些异常处理工作。比如,下面示例代码实现了一次具有 30s 超时的 select:

func worker() {
  select {
  case <-c:
       // ... do some stuff
  case <-time.After(30 *time.Second): 
      return
  }
}

在应用带有超时机制的 select 时,我们要特别注意 timer 使用后的释放,尤其在大量创建 timer 的时候。

select实现心跳机制

结合 time 包的 Ticker,我们可以实现带有心跳机制的 select。这种机制让我们可以在监听 channel 的同时,执行一些周期性的任务

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

参考资料