1. selectGo中的一个控制结构,类似switch语句。
  2. 主要作用是处理异步通道操作,所有情况都会涉及通信操作,主要用于channel操作。
  3. 因此select会监听分支语句中通道的读写操作,当分支中的通道读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
  4. select语句会选择一组可以发送或接收操作中的一个分支继续执行,select没有条件表达式,一直等待case进入可运行状态。
  5. 总结:
    • select中的case语句必须是对通道的操作。
    • select中的default子句总是可运行的。
    • 如果有多个分支都可以运行,select会伪随机公平的选出一个执行,其他分支不会执行。
    • 如果没有可运行的分支,且有default语句,那么就会执行default的动作。
    • 如果没有可运行的分支,且没有default语句,select将阻塞,直到某个分支可以运行。
  6. 关于select关键字的运行原理在channel篇介绍。(如果您还不熟悉channel,参看channel后再阅读本篇文章) select(原理)

语法格式

  1. Go编程语言中select语句的语法如下:
1
2
3
4
5
6
7
8
select {
    case communication clause: 
        statement(s)
    case communication clause: 
        statement(s)
    default:
        statement(s)
}

使用示例

  1. nil channel使用select default结构。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
    var c1, c2, c3 chan int // nil
    var i1, i2 int

    // 由于selectgo函数的源代码可知,go会把所有非nil的channel组成一个send+recv集
    // 然后all channel lock 伪随机的遍历case集。
    // 由于这里的channel都是nil,则不存在case集,直接走default
    select {
    case i1 = <-c1:
        fmt.Printf("received ", i1, " from c1\n")
    case c2 <- i2:
        fmt.Printf("sent ", i2, " to c2\n")
    case i3, ok := <-c3:
        if ok {// ok 为 true,表示正常获取到值 i3
            fmt.Printf("received ", i3, " from c3\n")
        } else {// ok 为 false,表示channel因关闭而触发
            fmt.Printf("c3 is closed\n")
        }
    default: // 走默认分支
        fmt.Printf("no communication\n")
    }

    // Output:
    // no communication
}
  1. 不存在default分支时。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
    "fmt"
    "time"
)

func main()  {
    var c1, c2, c3 chan int // nil
    var i1, i2 int

    // 由于selectgo函数的源代码可知,go会把所有非nil的channel组成一个send+recv集
    // 然后all channel lock 伪随机的遍历case集。
    // 由于这里只有Timer.C这个chan在case集中,则获取lock然后从这里读取数据,不能立刻完成则被挂起等待。
    select {
    case i1 = <-c1:
        fmt.Printf("received ", i1, "from c1\n")
    case c2 <- i2:
        fmt.Printf("sent ", i2, "to c2\n")
    case i3, ok := (<-c3):
        if ok {
            fmt.Printf("received ", i3, "from c3\n")
        } else {
            fmt.Printf("c3 is closed\n")
        }
        // func After(d Duration) <-chan Time
        // After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C
    case <-time.After(time.Second * 3): // 超时退出
        fmt.Println("request time out")
    }

    // Output:
    // request time out
}

常用用法

  1. 超时判断。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "fmt"
    "time"
)

var resChan = make(chan int)

func main() {
    test()  // request time out

    // Output:
    // request time out
}

func test() {
    // 1. case集是send+recv的集合,不包含default
    // 2. 获取所有 all channel lock
    // 3. 伪随机遍历所有的case集,寻找是否有立刻完成channel。
    // 4. 如果都没有则以sudog形式封装当前goroutine挂在所有的channel上,all channel unlock 再次调度循环等待唤醒。
    // 5. 当goroutine被唤醒,处理所有挂在channel上的sudog,然后返回,因为数据交换已经在唤醒前处理了。
    select {
    case data := <-resChan:             // 等待从resChan中读取数据
        doData(data)
    case <-time.After(time.Second * 3): // 3秒后会像time.C通道中写入当前时间,这里得到选中
        fmt.Println("request time out")
    }
}

func doData(data int) {
    fmt.Println("doData:", data)
}
  1. 判断channel是否阻塞。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
ch := make (chan int, 5)
// ...
data= 0

// tryLock 形式,是否能立即完成。
select {
case ch <- data:
default:
    //做相应操作,比如丢弃data。视需求而定
}

select作用在channel

c<-v

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// compiler implements
//
//  select {
//  case c <- v:
//      ... foo
//  default:
//      ... bar
//  }
//
// as
//
//  if selectnbsend(c, v) {
//      ... foo
//  } else {
//      ... bar
//  }
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
    // selected返回值 true.该分支被选中 false.该分支不会被选中
    return chansend(c, elem, false, getcallerpc())
}

v<-c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// compiler implements
//
//  select {
//  case v = <-c:
//      ... foo
//  default:
//      ... bar
//  }
//
// as
//
//  if selectnbrecv(&v, c) {
//      ... foo
//  } else {
//      ... bar
//  }
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
    // chanrecv函数存在两个返回值 布尔
    // selected 返回值 true.该分支被选中 false.该分支不会被选中
    // 第二个返回值,当前是否读取数据成功, false.读取失败 true.读取成功
    selected, _ = chanrecv(c, elem, false)
    return
}

v, ok = <-c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// compiler implements
//
//  select {
//  case v, ok = <-c:
//      ... foo
//  default:
//      ... bar
//  }
//
// as
//
//  if c != nil && selectnbrecv2(&v, &ok, c) {
//      ... foo
//  } else {
//      ... bar
//  }
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
    // TODO(khr): just return 2 values from this function, now that it is in Go.
    // chanrecv函数存在两个返回值 布尔值
    // selected 返回值 true.该分支被选中 false.该分支不会被选中
    // 第二个返回值,当前是否读取数据成功, false.读取失败 true.读取成功
    selected, *received = chanrecv(c, elem, false)
    return
}