【Go语言分析 select case 】

2023-12-14 19:11:55

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的方式来实现一个伪优先级任务队列

文章来源:https://blog.csdn.net/meihaoshy/article/details/134923551
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。