没有不值得去解决的问题,也没有不值得去学习的技术!

Go 语言复习:使用 select 解决 Channel 读取阻塞问题

运行结果:如图1

作者:

, ,

Go 并发编程实战步步通

其中一次执行结果输出了 hello,world 11 。小概率事件,或者说 执行结果大概率不会输出 “hello,world 12”及以后的输出。以下是一个简单的执行示意图1

(1) goroutine快速入门:多场景执行流程解析(附代码+流程图)

运行后,控制台要么打印空结果,要么只打印少量结果,大部分阶乘结果丢失。

(2) Go并发学习:从阶乘案例看Channel的必要性

测试代码(故意混用类型,验证报错) 测试结果(编译报错):如图1

(3) Go学习笔记:Channel基础用法与4种核心注意事项(附代码测试)

我运行了上述代码,整理出核心运行结果(截取部分),方便自己后续回顾:如图1

(4) Go复习笔记:Goroutine与Channel协同工作(解决加锁加休眠隐患)

Go 语言通道阻塞深度解析:从 Deadlock 到异步处理

(5) Go 语言通道阻塞深度解析:从 Deadlock 到异步处理

在我的测试环境(2 核 CPU)下,统计 1-200,000 的素数,结果如下:如图1 - 传统串行实现:耗时约 25 秒。 - 多协程并发实现:耗时约 12 秒。

(6) Go 语言并发实战:素数统计性能优化对比(“传统串行实现”与“多协程并发实现”)

运行结果:如图1

(7) Go 语言复习:使用 select 解决 Channel 读取阻塞问题

运行结果:如图1

(8) Go 语言中 Goroutine 的 Panic 捕获与 recover 机制

问题背景

在 Go 语言中,当我们从一个未关闭的 channel 读取数据时,如果 channel 中没有数据,读操作会阻塞。传统的做法是遍历 channel 直到它被关闭,但在实际开发中,我们可能无法确定何时应该关闭 channel,或者多个 goroutine 同时向 channel 写入数据时,关闭时机难以把握。

如果不妥善处理,就会导致 deadlock(死锁)。

解决方案:select 语句

select 语句可以让程序同时等待多个 channel 操作,当任何一个 case 可以执行时,就会执行该 case。配合 default 分支,可以实现非阻塞的 channel 读取。

代码示例

让我们看一个具体的例子:

Go
package main

import (
	"fmt"
	"time"
)

func main() {
	// 1. 定义一个容量为 10 的 int 类型 channel
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}

	// 2. 定义一个容量为 5 的 string 类型 channel
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		stringChan <- "hello" + fmt.Sprintf("%d", i)
	}

	// 使用 select 循环从多个 channel 读取数据
	for {
		select {
		case v := <-intChan:
			fmt.Printf("从 intChan 读取的数据: %d\n", v)
			time.Sleep(time.Second)
		case v := <-stringChan:
			fmt.Printf("从 stringChan 读取的数据: %s\n", v)
			time.Sleep(time.Second)
		default:
			fmt.Printf("都取不到了,退出程序\n")
			time.Sleep(time.Second)
			return
		}
	}
}

运行结果:如图1

运行结果:如图1
Plaintext
/app/go-atguigu/channel-details # go run main.go 
从 stringChan 读取的数据: hello0
从 stringChan 读取的数据: hello1
从 stringChan 读取的数据: hello2
从 stringChan 读取的数据: hello3
从 intChan 读取的数据: 0
从 intChan 读取的数据: 1
从 intChan 读取的数据: 2
从 stringChan 读取的数据: hello4
从 intChan 读取的数据: 3
从 intChan 读取的数据: 4
从 intChan 读取的数据: 5
从 intChan 读取的数据: 6
从 intChan 读取的数据: 7
从 intChan 读取的数据: 8
从 intChan 读取的数据: 9
都取不到了,退出程序
/app/go-atguigu/channel-details # 

代码解析

1. 创建带缓冲的 channel

我们创建了两个带缓冲的 channel:
– `intChan`:容量为 10,写入 10 个整数
– `stringChan`:容量为 5,写入 5 个字符串

由于是带缓冲的 channel,写入操作不会阻塞,直到缓冲区满。

2. select 多路复用

核心的 for-select 结构:

Go
for {
    select {
    case v := <-intChan:
        // 处理 intChan 的数据
    case v := <-stringChan:
        // 处理 stringChan 的数据
    default:
        // 两个 channel 都没有数据时执行
        return
    }
}

关键点:
– select 会随机选择一个可执行的 case
– 如果多个 case 都可执行,随机选择一个
– 如果没有 case 可执行且有 default 分支,立即执行 default
– 如果没有 case 可执行且没有 default,select 会阻塞

3. default 分支的作用

default 分支是解决阻塞问题的关键:

– 当两个 channel 都没有数据时,不会阻塞等待
– 而是立即执行 default 分支
– 在 default 中我们可以决定退出循环或执行其他逻辑

这样就避免了因为等待一个永远不会有关闭信号的 channel 而导致的死锁。

与传统方式的对比

传统方式的问题:

Go
// 需要明确关闭 channel
close(intChan)
for v := range intChan {
    fmt.Println(v)
}

这种方式要求我们必须知道何时关闭 channel,在多 goroutine 场景下可能比较复杂。

select 方式的优势:
– 不需要显式关闭 channel
– 可以同时监听多个 channel
– 通过 default 实现非阻塞读取
– 更灵活的控制流程

注意事项

1. 性能考虑:示例中每次读取后都 sleep 1 秒,实际应用中应根据业务需求调整

2. 公平性:select 在多个 case 可用时是随机选择的,不保证公平性

3. 资源释放:虽然不需要关闭 channel 来避免死锁,但在适当的时候关闭 channel 仍然是好的实践,可以让接收方知道不会再有新数据

4. break 标签:如果需要从 select 中跳出外层循环,可以使用标签:

Go
   label:
   for {
       select {
       case v := <-ch:
           if condition {
               break label
           }
       }
   }
   

总结

select 语句是 Go 语言并发编程中的重要工具,它提供了:
– 多 channel 的同时监听
– 非阻塞的 channel 操作(通过 default)
– 优雅的超时控制
– 灵活的并发流程管理

在实际开发中,合理使用 select 可以让我们的并发代码更加健壮和易维护。特别是在处理多个数据源、实现超时机制、或者避免死锁场景时,select 都是非常实用的工具。

希望这次复习能帮助大家更好地理解和应用 Go 语言的 select 机制!

Go 语言并发实战:素数统计性能优化对比(“传统串行实现”与“多协程并发实现”) Go 语言中 Goroutine 的 Panic 捕获与 recover 机制

Go / Gin 后端接口维护与远程技术支持

如果你的 Go / Gin 后端接口、小型服务或内部系统需要维护、Bug 修复、接口排查、性能优化或小功能迭代,可以联系我。

适合以下情况:
✅ Go / Gin 接口报错或响应变慢
✅ 已有 Go 服务需要长期维护
✅ 后端 API 需要小功能迭代
✅ 定时任务、数据处理服务异常
✅ MySQL / Redis / Nginx 相关问题排查
✅ 需要远程兼职技术支持

可先从一次小问题开始:
✅ 接口异常排查
✅ 日志分析
✅ 性能瓶颈定位
✅ 部署环境检查
✅ 小功能评估与迭代

如需咨询,请联系我,并注明:Go / Gin 维护咨询

联系方式:
Telegram:@shuijingwan
微信:13980074657
邮箱:shuijingwanwq@gmail.com

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理