Go 语言内置 I/O 多路复用机制

简介: Go 语言内置 I/O 多路复用机制

介绍

Go 协程之间通过 channel 通信,但是 channel 读写取决于自身特性,即是否有可写入缓冲区、缓冲区中是否有数据、是否已关闭...

为了检测 channel 的特性,Go 提供了一个关键字 select,可用于实现 I/O 多路复用机制。

本文我们介绍 Go 关键字 select 的使用方式。

使用方式

Go 关键字 select 中包含 case 语句和 default 语句,其中 default 语句可以认为是一种特殊的 case 语句。

因为 default 语句不负责处理 channel 的读写,它可以在 select 中的任意位置,且仅能包含一个 default 语句。在所有 case 语句都不满足执行条件时,default 语句将被执行(建议尽量不要省略 default 语句)。

我们通过代码片段,分别介绍 select 在检测到 channel 不同特性时,得到的运行结果。

接下来,我们阅读一段代码。

func main() {
 fmt.Println("Golang 语言开发栈")
 go func() {
  fmt.Println("Golang 公众号")
 }()
}

阅读上面这段代码,读者朋友们认为 Go 协程中的打印语句可以正常输出吗?

读者朋友们如果运行代码,会发现 Go 协程中的打印语句还没有执行,程序就已经退出了,这是因为 main 函数中的打印语句已经执行完成,所以会退出程序。

如果我们希望 Go 协程中的打印语句也执行,可以在 main 函数中使用 select{}main 阻塞,Go 协程中的打印语句就有机会执行了。但是,这会导致死锁(可以根据实际应用场景选择是否使用)。

接下来,我们再读一段可以导致死锁的代码:

func main() {
 c := make(chan string)
 DoChannel(c)
}
func DoChannel(c chan string) {
 var receive string
 send := "golang"
 select {
 case receive = <-c:
  fmt.Println(receive)
 case c <- send:
  fmt.Println(send)
 }
}

阅读上面这段代码,我们定义一个函数 DoChannel(),该函数接收的参数是一个 string 类型的 channel,函数体中使用 select 中的两个 case 语句,分别对参数进行接收和发送操作。

因为,我们传参的 c 是无缓冲 channel,所以它即不能读也不能写,两个 case 语句都不执行,select 陷入阻塞,导致死锁(此处为了行文,故意没有 default 语句)。

我们将上面这段代码,稍微修改一下,将入参的 c 改为 1 个缓冲区大小的 channel(未写入数据)。代码如下:

func main() {
 c := make(chan string, 1)
 DoChannel(c)
}

运行代码,写执行,读未执行。

select 中的对入参 channel 进行发送操作的 case 语句被执行,因为入参 c 是一个有 1 个缓冲区大小的 channel,并且该 channel 中还没有数据,所以读取操作的 case 语句没有读取到数据,不满足执行条件。

有缓冲区,已写满数据 channel

我们再修改一下入参 c,将入参的 c 改为 1 个缓冲区大小的 channel,并且写入字符串 Go。代码如下:

func main() {
 c := make(chan string, 1)
 c <- "Go"
 DoChannel(c)
}

运行代码,读执行,写未执行。

select 中的对入参 channel 进行接收操作的 case 语句被执行,因为入参 c 是一个有 1 个缓冲区大小,并且已写满数据,所以读取操作的 case 语句可以读取到数据,满足执行条件。

而写入操作的 case 无法写入数据,不满足执行条件。

有缓冲区,有数据,可写数据 channel

最后一种场景是既能读取也能写入的 channel,我们修改一下入参 c,将入参 c 改为 2 个缓冲区大小的 channel,其中 1 个缓冲区写入字符串 Go,另外 1 个缓冲区还可以写入数据。代码如下:

func main() {
 c := make(chan string, 2)
 c <- "Go"
 DoChannel(c)
}

通过多次运行代码,会发现读取和写入的 case 语句都有机会执行,因为两个 case 语句都满足执行条件,但是只能有 1 个 case 语句执行,select 会随机执行其中 1 个 case 语句。

至此,我们已经介绍了 5 种 channelselect 中的运行结果。

case 语句中声明变量

上面的代码中,我们发现在两个 case 语句中,读操作我们将读取到的数据赋值给变量 receive,实际上,我们也可以省略变量赋值操作。

如果我们需要将读取到的数据,赋值给变量的话,一般建议将读取 channel 返回的两个值全部接收,其中一个是读取到的数据,另外一个是布尔值,代表 channel 中没有数据,并且已被关闭。代码如下:

func main() {
 c := make(chan string)
 close(c)
 DoChannelV2(c)
}
func DoChannelV2(c chan string) {
 var (
  receive string
  ok      bool
 )
 select {
 case receive, ok = <-c:
  if !ok {
   fmt.Println("no data")
  } else {
   fmt.Println(receive)
  }
 }
}

阅读上面这段代码,我们使用 closec 关闭。select 中的读操作 case 语句,可以通过 ok 的值,得到 channel 中没有数据,且已被关闭,不必打印空数据。

03

总结

本文我们了解到 select 中的 case 语句可以读取 channel,多个 case 语句仅能其中 1 个被执行。

每个 case 语句仅能对 1 个 channel 进行读写操作,如果读操作未读取到数据将陷入阻塞,如果写操作无法写入数据将陷入阻塞,如果所有 case 语句中的 channel 都陷入阻塞时,select 也会陷入阻塞。

为了避免 select 陷入阻塞,我们可以使用 default 语句,需要注意的是,default 语句可以在 select 的任意位置,但是仅能包含 1 个,而 case 语句可以包含多个。


目录
相关文章
|
7天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
26 2
|
5天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
15 2
|
5天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
19 2
|
9天前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
86 58
|
8天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
5天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
14 4
|
5天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
20 1
|
9天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
9天前
|
Go 数据处理 调度
探索Go语言的并发模型:Goroutines与Channels的协同工作
在现代编程语言中,Go语言以其独特的并发模型脱颖而出。本文将深入探讨Go语言中的Goroutines和Channels,这两种机制如何协同工作以实现高效的并发处理。我们将通过实际代码示例,展示如何在Go程序中创建和管理Goroutines,以及如何使用Channels进行Goroutines之间的通信。此外,本文还将讨论在使用这些并发工具时可能遇到的常见问题及其解决方案,旨在为Go语言开发者提供一个全面的并发编程指南。
|
7天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。