Go 语言, select 浅析

简介: select 是 GO 语言中用来提供 IO 复用的机制,它可以检测多个 chan 是否 ready(可读/可写)
  1. 下面程序输出什么?
package main
import (
  "fmt"
  "time"
)
func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  go func() {
    chan1 <- 1
    time.Sleep(5 * time.Second)
  }()
  go func() {
    chan2 <- 1
    time.Sleep(5 * time.Second)
  }()
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
    default:
      fmt.Println("default")
  }
  fmt.Println("main exit")
}
复制代码

答案:

select 中的 case 执行顺序是随机的,如果某个 case 中的 channel 已经 ready,那么就会执行相应的语句并退 出 select 流程,如果所有 case 中的 channel 都未 ready,那么就会执行 default 中的语句然后退出 select 流程。

由于启动的协程和 select 语句并不能保证执行的顺序,所以也有可能 select 执行时协程还未向channel中写入数据,所以 select 直接执行 default 语句并退出。因此,次程序有可能产生三种输出:

chan1
main exit
复制代码
chan2
main exit
复制代码
default
main exit
复制代码
  1. 下面程序输出什么?
package main
import (
  "fmt"
  "time"
)
func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  writeFlag := false
  go func() {
    for {
      if writeFlag {
        chan1 <- 1
      }
      time.Sleep(5 * time.Second)
    }
  }()
  go func() {
    for {
      if writeFlag {
        chan2 <- 1
      }
      time.Sleep(5 * time.Second)
    }
  }()
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
  }
  fmt.Println("main exit.")
}
复制代码

答案:

和第一题一样,select 会随机检测各 case 语句中 channel是否 ready,如果有 case 中 channel 已经 ready 则执行相应的 case 语句后退出 select 流程,如果所有的 channel 都未 ready 且没有 default 的话,则会阻 塞等待各个 channel。因此上述程序会一直阻塞。

  1. 下面程序输出什么?
package main
import (
  "fmt"
)
func main() {
  chan1 := make(chan int)
  chan2 := make(chan int)
  go func() {
    close(chan1)
  }()
  go func() {
    close(chan2)
  }()
  select {
    case <- chan1:
      fmt.Println("chan1")
    case <- chan2:
      fmt.Println("chan2")
  }
  fmt.Println("main exit.")
}
复制代码

答案:

select 会随机检测各 case 语句中 channel 是否 ready,注意已关闭的 channel 也是可读的,所以上述程序中select 不会阻塞,具体执行哪个 case 语句具是随机的。

  1. 下面程序输出什么?
package main
func main() {
  select {
  }
}
复制代码

答案: 对于空的 select 语句,程序会被阻塞,确切的说是当前协程被阻塞,同时 Go 自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会发生 panic。所以上述程序会 panic。

实现原理

Go 实现 select 时,定义了一个数据结构表示每个 case 语句(包含defaut),select 执行过程可以类比成一个函数,函数输入 case 数组,输出选中的 case,然后程序流程转到选中的 case块。

源码包 src/runtime/select.go 定义了表示case语句的数据结构:

// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's scasetype.
type scase struct {
  c    *hchan         // chan
  elem unsafe.Pointer // data element
}
复制代码
  • c : 表示当前 case 语句所操作的 channel 指针
  • elem :表示缓冲区地址

归纳总结

  • select 语句中除 default 外,每个 case 操作一个channel,要么读要么写
  • select语句中除 default 外,各 case 执行顺序是随机的
  • select 语句中如果没有 default 语句,则会阻塞等待任一 case
  • select 语句中读操作要判断是否成功读取,关闭的 channel 也可以读取


相关文章
|
6天前
|
安全 网络协议 Go
Go语言网络编程
【10月更文挑战第28天】Go语言网络编程
94 65
|
6天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
29 13
|
2天前
|
测试技术 Go
go语言中测试工具
【10月更文挑战第22天】
10 4
|
2天前
|
SQL 关系型数据库 MySQL
go语言中数据库操作
【10月更文挑战第22天】
12 4
|
2天前
|
缓存 前端开发 中间件
go语言中Web框架
【10月更文挑战第22天】
13 4
|
6天前
|
网络协议 安全 Go
Go语言的网络编程基础
【10月更文挑战第28天】Go语言的网络编程基础
23 8
|
5天前
|
Go
go语言的复数常量
【10月更文挑战第21天】
18 6
|
5天前
|
Go
go语言的浮点型常量
【10月更文挑战第21天】
13 4
|
5天前
|
编译器 Go
go语言的整型常量
【10月更文挑战第21天】
16 3
|
6天前
|
Go
go语言编译时常量表达式
【10月更文挑战第20天】
16 3