前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >16.Go语言-Select

16.Go语言-Select

原创
作者头像
面向加薪学习
发布2022-09-04 11:16:40
2360
发布2022-09-04 11:16:40
举报
文章被收录于专栏:面向加薪学习面向加薪学习

第 16 章 Select

select 语句用在多个发送/接收通道操作中进行选择。

  • select 语句会一直阻塞,直到发送/接收操作准备就绪。
  • 如果有多个通道操作准备完毕, select 会随机地选取其中之一执行。

select 语法如下:

代码语言:go
复制
select {
    case expression1:
        code
    case expression2:
        code
    default:
        code
}

下面是使用 select-case 的一个简单例子:

代码语言:go
复制
package main

import "fmt"

func main() {
    // 创建3个通道
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
    ch3 := make(chan string, 1)
    // 往通道 1 发送数据 Go语言微服务架构核心22讲
    ch1 <- "Go语言微服务架构核心22讲"
    // 往通道 2 发送数据 从0到Go语言微服务架构师
    ch2 <- "从0到Go语言微服务架构师"
    // 往通道 3 发送数据 Go语言极简一本通
    ch3 <- "Go语言极简一本通"

    select {
    // 如果从通道 1 收到数据
    case message1 := <-ch1:
        fmt.Println("ch1 received:", message1)
    // 如果从通道 2 收到数据
    case message2 := <-ch2:
        fmt.Println("ch2 received:", message2)
    // 如果从通道 3 收到数据
    case message3 := <-ch3:
        fmt.Println("ch3 received:", message3)
    // 默认输出
    default:
        fmt.Println("No data received.")
    }
}

上面的程序创建了 3 个通道,并在执行 select 语句之前往通道 1 、通道 2 和 通道 3 分别发送数据,在执行 select 语句时,如果有机会的话会运行所有表达式,只要其中一个通道接收到数据,那么就会执行对应的 case 代码,然后退出。所以运行该程序可能输出下面的语句:

代码语言:shell
复制
ch1 received: Go语言微服务架构核心22讲

也有可能输出下面的这条语句,具体看哪个通道首先接收到数据:

代码语言:shell
复制
ch2 received: 从0到Go语言微服务架构师
ch3 received: Go语言极简一本通

16.1 select 的应用

每个任务执行的时间不同,使用 select 语句等待相应的通道发出响应。select 会选择首先响应先完成的 task,而忽略其它的响应。使用这种方法,我们可以做多个 task,并给用户返回最快的 task 结果。

下面的程序模拟了这种服务:

代码语言:go
复制
package main

import (
    "fmt"
    "time"
)

func task1(ch chan string) {
    time.Sleep(5 * time.Second)
    ch <- "Go语言极简一本通"
}

func task2(ch chan string) {
    time.Sleep(7 * time.Second)
    ch <- "Go语言微服务架构核心22讲"
}

func task3(ch chan string) {
    time.Sleep(2 * time.Second)
    ch <- "从0到Go语言微服务架构师"
}

func main() {
    // 创建两个通道
    ch1 := make(chan string)
    ch2 := make(chan string)
    ch3 := make(chan string)
    go task1(ch1)
    go task2(ch2)
    go task3(ch3)

    select {
        // 如果从通道 1 收到数据
        case message1 := <-ch1:
            fmt.Println("ch1 received:", message1)
        // 如果从通道 2 收到数据
        case message2 := <-ch2:
            fmt.Println("ch2 received:", message2)
        // 如果从通道 3 收到数据
        case message3 := <-ch3:
            fmt.Println("ch3 received:", message3)
	}
}

当然,上面的程序会发现,没有 default 分支,因为如果加了该默认分支,如果还没从通道接收到数据, select 语句就会直接执行 default 分支然后退出,而不是被阻塞。

16.2 造成死锁

上面的例子引出了一个新的问题,那就是如果没有 default 分支, select 就会阻塞,如果一直没有命中其中的某个 case 最后会造成死锁。

代码语言:go
复制
package main

import (
    "fmt"
)

func main() {
    // 创建两个通道
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
    ch3 := make(chan string, 1)

    select {
    // 如果从通道 1 收到数据
    case message1 := <-ch1:
        fmt.Println("ch1 received:", message1)
    // 如果从通道 2 收到数据
    case message2 := <-ch2:
        fmt.Println("ch2 received:", message2)
	// 如果从通道 3 收到数据
    case message3 := <-ch3:
        fmt.Println("ch3 received:", message3)
    }
}

运行上面的程序会造成死锁。解决该问题的方法是写好 default 分支。

当然还有另一种情况会导致死锁的发生,那就是使用空 select

代码语言:go
复制
package main

func main() {
    select {}
}

运行上面的程序会抛出 panic

Tips:

  • 前面学习 switch-case 的时候,里面的 case 是顺序执行的,但在 select 里并不是顺序执行的。在上面的第一个例子就可以看出,当 select 由多个 case 准备就绪时,将会随机地选取其中之一去执行。

16.3 select 超时处理

case 里的通道始终没有接收到数据时,而且也没有 default 语句时, select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间。

代码语言:go
复制
package main

import (
    "fmt"
    "time"
)

func makeTimeout(ch chan bool, t int) {
    time.Sleep(time.Second * time.Duration(t))
    ch <- true
}

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    c3 := make(chan string, 1)
    timeout := make(chan bool, 1)

    go makeTimeout(timeout, 2)

    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    case msg3 := <-c3:
        fmt.Println("c3 received: ", msg3)
    case <-timeout:
        fmt.Println("Timeout, exit.")
    }
}

16.4 读取/写入数据

select 里的 case 表达式只能对通道进行操作,不管你是往通道写入数据,还是从通道读出数据。

代码语言:go
复制
package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 2)

    c1 <- "从0到Go语言微服务架构师"
    select {
    case c1 <- "Go语言微服务架构核心22讲":
        fmt.Println("c1 received: ", <-c1)
        fmt.Println("c1 received: ", <-c1)
    default:
        fmt.Println("channel blocking")
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 16 章 Select
    • 16.1 select 的应用
      • 16.2 造成死锁
        • 16.3 select 超时处理
          • 16.4 读取/写入数据
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档