编程 Golang Select 的使用及基本实现

2024-11-18 13:48:21 +0800 CST views 825

Golang Select 的使用及基本实现

1.1 Select 简介

在 Go 语言中,select 语句用于处理多个通道操作,帮助开发者解决并发编程中的通信和同步问题。select 语句类似于 switch,但每个 case 必须是通道操作。

基本语法:

select {
case <-chan1:
    // 当 chan1 有数据时执行
case chan2 <- value:
    // 当 chan2 可以发送数据时执行
default:
    // 当没有满足的 case 时执行
}

使用场景

  • 多通道选择:同时等待多个通道操作,执行第一个准备好的通道。
  • 超时处理:结合 time.After 实现超时机制。
  • 非阻塞通信:使用 default 子句实现非阻塞通道操作。

1.2 Select 与通道

selectcase 表达式必须涉及通道操作(读写),可以避免等待某个阻塞通道导致的程序死锁。

示例 1:从多个通道接收数据

package main

import (
    "fmt"
    "time"
)

func main() {
    chan1 := make(chan string)
    chan2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        chan1 <- "Message from channel 1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        chan2 <- "Message from channel 2"
    }()

    select {
    case msg1 := <-chan1:
        fmt.Println(msg1)
    case msg2 := <-chan2:
        fmt.Println(msg2)
    }
}

此示例中,select 等待两个通道的数据,并输出第一个准备好的通道的消息。

示例 2:处理超时情况

package main

import (
    "fmt"
    "time"
)

func main() {
    chan1 := make(chan string)
    chan2 := make(chan string)

    go func() {
        time.Sleep(3 * time.Second)
        chan1 <- "Message from channel 1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        chan2 <- "Message from channel 2"
    }()

    select {
    case msg1 := <-chan1:
        fmt.Println(msg1)
    case msg2 := <-chan2:
        fmt.Println(msg2)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout, no channel was ready within 1 second")
    }
}

在该示例中,select 会在 1 秒内等待 chan1chan2,如果超过 1 秒未有通道准备好,则输出超时消息。

示例 3:非阻塞通信

package main

import (
    "fmt"
)

func main() {
    chan1 := make(chan string)
    select {
    case msg := <-chan1:
        fmt.Println(msg)
    default:
        fmt.Println("Default case: Channel is not ready")
    }
}

如果 chan1 没有数据可接收,select 会执行 default 子句,避免阻塞。

1.3 Select 的实现

1.3.1 数据结构

在 Go 语言的源码中,select 语句的 case 通过 runtime.scase 结构体来表示:

type scase struct {
    c    *hchan         // 通道
    elem unsafe.Pointer // 数据元素
}

1.3.2 常见流程

编译器将 select 语句转换为包含通道信息的 runtime.scase 结构体,并调用 runtime.selectgo 函数从多个就绪的通道中选择一个 scase 来执行。

1.3.3 随机轮询与加锁顺序

runtime.selectgo 函数通过随机轮询和排序加锁的方式处理 select 语句,以避免通道饥饿和死锁。

  • 轮询顺序:通过随机函数 runtime.cheaprandn 确定。
  • 加锁顺序:根据通道地址排序,避免死锁。

1.4 性能考量

  • 注册和选择的开销:每个通道操作都需要注册到调度队列,增加调度开销。
  • 随机选择的开销:多通道就绪时,会扫描所有就绪通道,带来额外开销。
  • 阻塞与唤醒的开销:涉及 goroutine 调度,增加上下文切换开销。

1.5 I/O 多路复用

Go 语言中的 select 与操作系统中的 select 系统调用类似,可以监听多个文件描述符或通道的可读写状态,实现 I/O 多路复用。

1.6 总结

select 语句在 Go 中提供了一个语言层面的 I/O 多路复用机制,专门用于检测多个通道的可读或可写状态。这使得并发编程中的通信更加高效和灵活。

复制全文 生成海报 编程 Go语言 并发编程

推荐文章

Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
如何在Vue 3中使用Ref访问DOM元素
2024-11-17 04:22:38 +0800 CST
`Blob` 与 `File` 的关系
2025-05-11 23:45:58 +0800 CST
liunx宝塔php7.3安装mongodb扩展
2024-11-17 11:56:14 +0800 CST
Nginx 负载均衡
2024-11-19 10:03:14 +0800 CST
JavaScript中的常用浏览器API
2024-11-18 23:23:16 +0800 CST
OpenCV 检测与跟踪移动物体
2024-11-18 15:27:01 +0800 CST
JavaScript 的模板字符串
2024-11-18 22:44:09 +0800 CST
支付轮询打赏系统介绍
2024-11-18 16:40:31 +0800 CST
你可能不知道的 18 个前端技巧
2025-06-12 13:15:26 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
Vue 中如何处理父子组件通信?
2024-11-17 04:35:13 +0800 CST
Vue3中的事件处理方式有何变化?
2024-11-17 17:10:29 +0800 CST
一键配置本地yum源
2024-11-18 14:45:15 +0800 CST
在JavaScript中实现队列
2024-11-19 01:38:36 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
Vue3中的Store模式有哪些改进?
2024-11-18 11:47:53 +0800 CST
程序员茄子在线接单