学会 Go select 语句,轻松实现高效并发
创始人
2025-05-28 14:21:35
0

耐心和持久胜过激烈和狂热。

哈喽大家好,我是陈明勇,本文介绍的内容是 Go select 语句。如果本文对你有帮助,不妨点个赞,如果你是 Go 语言初学者,不妨点个关注,一起成长一起进步,如果本文有错误的地方,欢迎指出!

前言

Go 语言中,GoroutineChannel 是非常重要的并发编程概念,它们可以帮助我们解决并发编程中的各种问题。关于它们的基本概念和用法,前面的文章 一文初探 Goroutine 与 channel 中已经进行了介绍。而本文将重点介绍 select,它是协调多个 channel 的桥梁。

select 介绍

什么是 select

selectGo 语言中的一种控制结构,用于在多个通信操作中选择一个可执行的操作。它可以协调多个 channel 的读写操作,使得我们能够在多个 channel 中进行非阻塞的数据传输、同步和控制。

为什么需要 select

Go 语言中的 select 语句是一种用于多路复用通道的机制,它允许在多个通道上等待并处理消息。相比于简单地使用 for 循环遍历通道,使用 select 语句能够更加高效地管理多个通道。

以下是一些 select 语句的使用场景:

  • 等待多个通道的消息(多路复用)

    当我们需要等待多个通道的消息时,使用 select 语句可以非常方便地等待这些通道中的任意一个通道有消息到达,从而避免了使用多个goroutine进行同步和等待。

  • 超时等待通道消息

    当我们需要在一段时间内等待某个通道有消息到达时,使用 select 语句可以与 time 包结合使用实现定时等待。

  • 在通道上进行非阻塞读写

    在使用通道进行读写时,如果通道没有数据,读操作或写操作将会阻塞。但是使用 select 语句结合 default 分支可以实现非阻塞读写,从而避免了死锁或死循环等问题。

因此,select 的主要作用是在处理多个通道时提供了一种高效且易于使用的机制,简化了多个 goroutine 的同步和等待,使程序更加可读、高效和可靠。

select 基础

语法

select {case <- channel1:// channel1准备好了case data := <- channel2:// channel2准备好了,并且可以读取到数据datacase channel3 <- data:// channel3准备好了,并且可以往其中写入数据datadefault:// 没有任何channel准备好了
}

其中, <- channel1 表示读取 channel1 的数据,data <- channel2 表示用 data 去接收数据;channel3 <- data 表示往 channel3 中写入数据。

select 的语法形式类似于 switch,但是它只能用于 channel 操作。在 select 语句中,我们可以定义多个 case,每个 case 都是一个 channel 操作,用于读取或写入数据。如果有多个 case 同时可执行,则会随机选择其中一个。如果没有任何可执行的 case,则会执行 default 分支(如果存在),或者阻塞等待直到至少有一个 case 可执行为止。

基本用法

package mainimport ("fmt""time"
)func main() {ch1 := make(chan int)ch2 := make(chan int)go func() {time.Sleep(1 * time.Second)ch1 <- 1}()go func() {time.Sleep(2 * time.Second)ch2 <- 2}()for i := 0; i < 2; i++ {select {case data, ok := <-ch1:if ok {fmt.Println("从 ch1 接收到数据:", data)} else {fmt.Println("通道已被关闭")}case data, ok := <-ch2:if ok {fmt.Println("从 ch2接收到数据: ", data)} else {fmt.Println("通道已被关闭")}}}select {case data, ok := <-ch1:if ok {fmt.Println("从 ch1 接收到数据:", data)} else {fmt.Println("通道已被关闭")}case data, ok := <-ch2:if ok {fmt.Println("从 ch2接收到数据: ", data)} else {fmt.Println("通道已被关闭")}default:fmt.Println("没有接收到数据,走 default 分支")}
}

执行结果

从 ch1 接收到数据: 1
从 ch2接收到数据:  2
没有接收到数据,走 default 分支

上述示例中,首先创建了两个 channelch1ch2,分别在不同的 goroutine 中向两个 channel 中写入数据。然后,在主 goroutine 中使用 select 语句监听两个channel,一旦某个 channel 上有数据流动,就打印出相应的数据。由于 ch1 中的数据比 ch2 中的数据先到达,因此首先会打印出 "从 ch1 接收到数据: 1",然后才打印出 "从 ch2接收到数据: 2"

为了方便测试 default 分支,我写了两个 select 代码块,执行到第二个 select 代码块的时候,由于 ch1ch2 都没有数据了,因此执行 default 分支,打印 "没有接收到数据,走 default 分支"

一些使用 select 与 channel 结合的场景

实现超时控制

package mainimport ("fmt""time"
)func main() {ch := make(chan int)go func() {time.Sleep(3 * time.Second)ch <- 1}()select {case data, ok := <-ch:if ok {fmt.Println("接收到数据: ", data)} else {fmt.Println("通道已被关闭")}case <-time.After(2 * time.Second):fmt.Println("超时了!")}
}

执行结果为:超时了!

在这个例子中,程序将在 3 秒后向 ch 通道里写入数据,而我在 select 代码块里设置的超时时间为 2 秒,如果在 2 秒内没有接收到数据,则会触发超时处理。

实现多任务并发控制

package mainimport ("fmt"
)func main() {ch := make(chan int)for i := 0; i < 10; i++ {go func(id int) {ch <- id}(i)}for i := 0; i < 10; i++ {select {case data, ok := <-ch:if ok {fmt.Println("任务完成:", data)} else {fmt.Println("通道已被关闭")}}}
}

执行结果(每次执行的顺序都会不一致):

任务完成: 1
任务完成: 5
任务完成: 2
任务完成: 3
任务完成: 4
任务完成: 0
任务完成: 9
任务完成: 6
任务完成: 7
任务完成: 8

在这个例子中,启动了 10 个 goroutine 并发执行任务,并使用一个 channel 来接收任务的完成情况。在主函数中,使用 select 语句监听这个 channel,每当接收到一个完成的任务时,就进行处理。

监听多个通道的消息

package mainimport ("fmt""time"
)func main() {ch1 := make(chan int)ch2 := make(chan int)// 开启 goroutine 1 用于向通道 ch1 发送数据go func() {for i := 0; i < 5; i++ {ch1 <- itime.Sleep(time.Second)}}()// 开启 goroutine 2 用于向通道 ch2 发送数据go func() {for i := 5; i < 10; i++ {ch2 <- itime.Sleep(time.Second)}}()// 主 goroutine 从 ch1 和 ch2 中接收数据并打印for i := 0; i < 10; i++ {select {case data := <-ch1:fmt.Println("Received from ch1:", data)case data := <-ch2:fmt.Println("Received from ch2:", data)}}fmt.Println("Done.")
}

执行结果(每次执行程序打印的顺序都不一致):

Received from ch2: 5
Received from ch1: 0
Received from ch1: 1
Received from ch2: 6
Received from ch1: 2
Received from ch2: 7
Received from ch1: 3
Received from ch2: 8
Received from ch1: 4
Received from ch2: 9
Done.

该示例代码中,通过使用 select 多路复用,可以同时监听多个通道的数据,并避免了使用多个 goroutine 进行同步和等待的问题。

使用 default 实现非阻塞读写

import ("fmt""time"
)func main() {ch := make(chan int, 1)go func() {for i := 1; i <= 5; i++ {ch <- itime.Sleep(1 * time.Second)}close(ch)}()for {select {case val, ok := <-ch:if ok {fmt.Println(val)} else {ch = nil}default:fmt.Println("No value ready")time.Sleep(500 * time.Millisecond)}if ch == nil {break}}
}

执行结果(每次执行程序打印的顺序都不一致):

No value ready
1
No value ready
2
No value ready
No value ready
3
No value ready
No value ready
4
No value ready
No value ready
5
No value ready
No value ready

这个代码中,使用了 default 分支来实现非阻塞的通道读取和写入操作。在 select 语句中,如果有通道已经准备好进行读写操作,那么就会执行相应的分支。但是如果没有任何通道准备好读写,那么就会执行 default 分支中的代码。

select 的注意事项

以下是关于 select 语句的一些注意事项:

  • select 语句只能用于通信操作,如 channel 的读写,不能用于普通的计算或函数调用。
  • select 语句会阻塞,直到至少有一个 case 语句满足条件。
    如果有多个 case 语句满足条件,则会随机选择一个执行。
  • 如果没有 case 语句满足条件,并且有 default 语句,则会执行 default 语句。
  • select 语句中使用 channel 时,必须保证 channel 是已经初始化的。
  • 如果一个通道被关闭,那么仍然可以从它中读取数据,直到它被清空,此时会返回通道元素类型的零值和一个布尔值,指示通道是否已关闭。

总之,在使用 select 语句时,要仔细考虑每个 case 语句的条件和执行顺序,避免死锁和其他问题。

总结

本文主要介绍了 Go 语言中的 select 语句。在文章中,首先介绍了 select 的基本概念,包括它是一种用于在多个通道之间进行选择的语句,以及为什么需要使用 select

接下来,文章详细介绍了 select 的基础知识,包括语法和基础用法。在语法方面,讲解了 select 语句的基本结构以及如何使用 case 子句进行通道选择。在基础用法方面,介绍了如何使用 select 语句进行通道的读取和写入操作,并讲解了一些注意事项。

在接下来的内容中,文章列举了一些使用 selectchannel 结合的场景。这些场景包括实现超时控制、实现多任务并发控制、监听多个通道的消息以及使用 default 实现非阻塞读写。对于每个场景,文章都详细介绍了如何使用 select 语句实现。

最后,文章总结了 select 的注意事项,包括选择的通道必须是可读或可写的通道、select 语句中的 case 子句必须是通道操作或者空的 default 子句,不能是其他类型的语句等等。

相关内容

热门资讯

安卓手机系统流畅版,极致性能与... 你有没有发现,最近你的安卓手机用起来是不是特别顺滑?没错,就是那种点屏幕就立刻响应的感觉,简直让人爱...
forest安卓系统换到苹果,... 你有没有想过,手机操作系统就像是我们生活中的不同道路,有时候,你可能觉得一条路走得太久了,想要换一条...
华为鸿蒙系统安卓平板,开启智能... 亲爱的读者们,你是否也像我一样,对科技圈的新鲜事儿充满好奇?今天,我要和你聊聊一个最近在科技圈掀起波...
安卓系统藏族软件下载,精选安卓... 安卓系统藏族软件下载:探索藏族文化的数字新篇章在数字化时代,手机已经成为我们生活中不可或缺的一部分。...
显示安卓系统耗电大,深度剖析原... 手机电量总是不够用?是不是觉得安卓系统耗电特别大?别急,今天就来给你揭秘安卓系统耗电的秘密,让你手机...
抽取原装安卓系统驱动,深度挖掘... 你有没有遇到过这种情况?手机里的安卓系统突然卡顿,或者某个应用突然罢工,这时候你是不是想给它来个“大...
安卓系统手机游戏排行,热门游戏... 你有没有发现,最近你的手机里是不是又多了一款游戏?没错,安卓系统手机游戏排行又更新了!今天,就让我带...
安卓系统叫AR 特效,安卓系统... 你知道吗?最近在安卓系统上出现了一个超级酷炫的新功能,它就是AR特效!是不是听起来就让人兴奋不已?那...
安卓系统特有的功能,解锁智能生... 你知道吗?安卓系统这个家伙,简直就是智能手机界的“全能选手”。它不仅拥有丰富的应用市场,还能给你带来...
iqoo 安卓系统王者跳帧,王... 最近有没有发现你的iqoo手机在玩王者荣耀时突然卡顿,画面跳帧,简直让人抓狂啊!别急,今天就来给你揭...
安卓系统平板画图,创意无限的艺... 你有没有想过,用平板画图竟然也能这么有趣呢?尤其是当你手握安卓系统平板的时候,那感觉简直就像拥有了整...
安卓系统韩文变成中文,安卓系统... 你是不是也遇到过这种情况?手机里突然冒出了韩文,而你却一头雾水,完全看不懂?别急,今天就来给你详细解...
国内邮箱注册安卓系统,轻松掌握... 你有没有想过,为什么你的手机里会有那么多邮箱呢?是不是每次注册新账号,都感觉像是在进行一场数字版的“...
苹果系统和安卓系统合作,跨界合... 你知道吗?最近科技圈可是炸开了锅,因为苹果系统和安卓系统竟然要联手合作啦!这可不是闹着玩的,两个在智...
安卓系统怎么篡改位置,轻松伪装... 你有没有想过,手机里的位置信息竟然也能被篡改?没错,就是那个我们平时用来导航、找餐馆、定位好友的安卓...
kindle 刷原生安卓系统,... 亲爱的读者们,你是否也有过这样的经历:拥有一台Kindle,却因为系统不够流畅而感到烦恼?别担心,今...
安卓点歌系统连电脑,打造个性化... 你有没有想过,你的安卓手机里的点歌系统竟然可以和电脑无缝连接呢?这听起来是不是很神奇?没错,今天就要...
那个电视搭载安卓系统,智能娱乐... 你有没有想过,家里的电视竟然也能搭载安卓系统?没错,就是那个曾经只存在于手机和平板电脑上的操作系统,...
安卓系统反黄软件,净化网络环境 你有没有发现,随着智能手机的普及,我们每天的生活越来越离不开这个小小的屏幕了。但是,你知道吗?在这个...
安卓怎么测试系统好坏,安卓系统... 你有没有想过,你的安卓手机是不是真的像你想象中那么强大呢?别急,今天就来给你揭秘,怎么测试安卓系统的...