【Go语言分析 select case 】
select是Go语言在语言层面上提供的一个多路复用机制 它可以检测多个channel是否就绪
基础用法
Go语言select有如下的几个特点
- select中各个case执行顺序是随机的
- 如果某个case中的channel已经ready 则执行相应的语句并退出select流程
- 如果所有的case的channel都没有ready 则有default会走default然后退出select 没有default select将阻塞直至channel ready
- case后面不一定是读channel 也可以写channel 只要是对channel的操作就可以
- 空的select语句将被阻塞 直至panic 但是通常情况下 我们不会使用空的select语句 因为这会导致一个协程一直阻塞
下面我们使用几段代码来一一讲解select的特点
- select中各个case执行顺序是随机的
select {
case <-chan1: // 如果chan1成功读取到数据 则执行该操作
// ...
case chan2 <- 1: // 如果chan2成被写入数据 则执行该操作
// ...
}
如果说此时 chan1成功读取到了数据的同时chan2成功被写入了数据 那么就会随机选择一个case执行
- select没有case 永久阻塞
select {
}
如果说我们使用一个协程执行了上面这段代码 那么在该协程会永久阻塞
注意 虽然说Go语言有死锁机制 会自动检测是否所有协程是否被阻塞 但是这是建立在所有协程都进入死锁的情况 如果说只是这一个协程阻塞 其他协程没有被阻塞那么此时不会发生panic错误的
- 如果所有的case的channel都没有ready 则有default会走default然后退出select 没有default select将阻塞直至channel ready
select {
case <-chan1:
// 如果chan1已经准备好接收数据,则执行此case。
// ...
case chan2 <- 1:
// 如果chan2已经准备好发送数据,则执行此case。
// ...
default:
// 如果上述channel都未准备好,立即执行default case。
// ...
}
如果说此时没有channel准备好 则立刻执行default语句
使用场景
超时管理
// 设置一个1秒的超时时间
timeout := time.After(1 * time.Second)
// 假设这是一个异步操作的结果通道
operationCh := make(chan string)
select {
case result := <-operationCh:
// 处理异步操作的结果
fmt.Println("操作成功:", result)
case <-timeout:
// 如果1秒钟内操作没有完成,会执行这个case
fmt.Println("操作超时")
}
如果说我们不想无限制的执行该select操作 那么我们可以设置一个类似超时器 设定一个超时时间 如果说在该时间内没有读取到数据 那么我们就终止该select
无阻塞获取值
// 创建一个通道
messageChan := make(chan string, 1) // 缓冲通道,避免死锁
// 非阻塞地从通道接收数据
select {
case msg := <-messageChan:
fmt.Println("收到消息:", msg)
default:
fmt.Println("没有新消息")
}
// 向通道发送数据
messageChan <- "Hello, World!"
// 再次非阻塞地从通道接收数据
select {
case msg := <-messageChan:
fmt.Println("收到消息:", msg)
default:
fmt.Println("没有新消息")
}
上面的代码就是一段典型的无阻塞获取值的代码
在第一个select中 我们没有读取到数据 所以说立即打印没有新消息
在第二个select中 我们读取到了新的数据 所以说会立即打印 hello world
类事件驱动循环
type node struct {
heartbeat chan struct{}
conn struct {
Close func()
}
}
func (n *node) heartbeatDetect() {
// 创建一个重置的定时器
timeoutDuration := 3 * time.Second
heartbeatTimer := time.NewTimer(timeoutDuration)
for {
select {
case <-n.heartbeat:
// 收到心跳信号,重置定时器
if !heartbeatTimer.Stop() {
<-heartbeatTimer.C
}
heartbeatTimer.Reset(timeoutDuration)
case <-heartbeatTimer.C:
// 心跳超时,关闭连接
n.conn.Close()
return
}
}
}
在这个代码中 heartbeatDetect方法属于node结构体 node具有一个heartbeat通道 用于接收心跳信号 以及一个conn字段模拟网络连接 其中包含一个Close方法来关闭连接
函数首先创建了一个心跳定时器heartbeatTimer 用于跟踪心跳的超时
在for循环中 select语句监听心跳通道和定时器的通道
当心跳信号到达(通过n.heartbeat通道接收到) 该函数会尝试停止定时器 并且如果通道中有未处理的超时事件 则将其清除 以避免收到虚假的超时 然后它会重置定时器 开始新一轮的心跳等待
如果在规定时间内没有收到心跳(定时器的通道heartbeatTimer.C发出信号) 则会执行关闭连接的逻辑 并退出函数
带优先级的任务队列
highPriority := make(chan string, 5) // 高优先级任务队列
lowPriority := make(chan string, 5) // 低优先级任务队列
// 添加任务到不同优先级的队列
go func() {
highPriority <- "紧急任务"
lowPriority <- "一般任务"
highPriority <- "非常紧急任务"
}()
// 处理任务
for {
select {
case task := <-highPriority:
// 首选处理高优先级任务
fmt.Println("处理高优先级任务:", task)
continue // 继续下一个循环迭代,确保再次检查高优先级队列
default:
}
select {
case task := <-highPriority:
// 再次检查高优先级任务队列,确保没有遗漏
fmt.Println("处理高优先级任务:", task)
case task := <-lowPriority:
// 如果没有高优先级任务,则处理低优先级任务
fmt.Println("处理低优先级任务:", task)
}
}
我们可以使用多次select的方式来实现一个伪优先级任务队列
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!