编程 Go 语言中的 `select` 使用及基本实现

2024-11-18 22:38:30 +0800 CST views 987

Go 语言中的 select 使用及基本实现

1.1 select 简介:

在 Go 语言中,select 语句用于处理多个通道操作,简化并发编程中的通信和同步问题。select 类似于 switch 语句,但它的每个 case 都必须是一个通道操作。

基本语法如下:

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

使用场景

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

1.2 select 与通道

switch 不同,select 中的每个 case 都必须与 Channel 的读写操作有关,避免等待某个阻塞的 Channel 导致死锁。

示例 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")
    }
}

此示例展示了如何结合超时机制处理多个通道操作。

示例 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")
    }
}

当通道未准备好时,default 子句会被执行,避免阻塞。

1.3 select 的实现

1.3.1 数据结构

select 没有直接对应的结构体,但在 Go 语言源码中,它通过 runtime.scase 结构体表示 select 控制结构中的 case

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

1.3.2 常见流程

编译器会将所有的 case 转换为 runtime.scase 结构体,并调用 runtime.selectgo 函数来从多个通道中选择一个。

1.3.3 随机轮询与加锁顺序

runtime.selectgo 函数中,首先确定轮询顺序和加锁顺序。轮询顺序通过 runtime.cheaprandn 函数引入随机性,而加锁顺序则按通道的地址排序,防止死锁。

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
    ...
    j := cheaprandn(uint32(norder + 1)) // 引入随机性
    pollorder[norder] = pollorder[j]
    pollorder[j] = uint16(i)
    ...
    sellock(scases, lockorder) // 加锁顺序
}

func sellock(scases []scase, lockorder []uint16) {
    for _, o := range lockorder {
        c := scases[o].c
        lock(&c.lock) // 按顺序锁定通道
    }
}

随机轮询可以避免通道饥饿问题,加锁顺序避免死锁。

1.4 性能考量

  • 注册和选择开销:每次 select 操作都涉及将通道操作注册到调度队列,并从中选择可执行的通道。
  • 随机选择开销:涉及随机数生成和对就绪通道的扫描。
  • 阻塞和唤醒开销:阻塞和唤醒 goroutine 也会引入上下文切换开销。

1.5 I/O 多路复用

Go 语言的 select 类似于操作系统中的 select 系统调用。比如 C 语言中的 select 可以同时监听多个文件描述符的状态,Go 的 select 则用于监听多个通道的状态。

1.6 总结

表面来看,Go 的 select 语句是一种用于 Channel 操作的专用语句,深层次理解,它是 Go 在语言层面提供的 I/O 多路复用机制。

更多信息请参考文章:

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

推荐文章

MySQL用命令行复制表的方法
2024-11-17 05:03:46 +0800 CST
Vue3中如何进行性能优化?
2024-11-17 22:52:59 +0800 CST
PHP中获取某个月份的天数
2024-11-18 11:28:47 +0800 CST
JavaScript设计模式:发布订阅模式
2024-11-18 01:52:39 +0800 CST
使用 sync.Pool 优化 Go 程序性能
2024-11-19 05:56:51 +0800 CST
MySQL数据库的36条军规
2024-11-18 16:46:25 +0800 CST
Vue3中的虚拟滚动有哪些改进?
2024-11-18 23:58:18 +0800 CST
Nginx 防止IP伪造,绕过IP限制
2025-01-15 09:44:42 +0800 CST
Python中何时应该使用异常处理
2024-11-19 01:16:28 +0800 CST
Nginx 状态监控与日志分析
2024-11-19 09:36:18 +0800 CST
解决 PHP 中的 HTTP 请求超时问题
2024-11-19 09:10:35 +0800 CST
Elasticsearch 条件查询
2024-11-19 06:50:24 +0800 CST
用 Rust 玩转 Google Sheets API
2024-11-19 02:36:20 +0800 CST
前端代码规范 - 图片相关
2024-11-19 08:34:48 +0800 CST
deepcopy一个Go语言的深拷贝工具库
2024-11-18 18:17:40 +0800 CST
如何在 Vue 3 中使用 TypeScript?
2024-11-18 22:30:18 +0800 CST
liunx服务器监控workerman进程守护
2024-11-18 13:28:44 +0800 CST
git使用笔记
2024-11-18 18:17:44 +0800 CST
支付宝批量转账
2024-11-18 20:26:17 +0800 CST
支付轮询打赏系统介绍
2024-11-18 16:40:31 +0800 CST
Rust 中的所有权机制
2024-11-18 20:54:50 +0800 CST
Vue3结合Driver.js实现新手指引功能
2024-11-19 08:46:50 +0800 CST
Vue3中怎样处理组件引用?
2024-11-18 23:17:15 +0800 CST
程序员茄子在线接单