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

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

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语言 并发编程 编程技巧

推荐文章

Elasticsearch 监控和警报
2024-11-19 10:02:29 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
五个有趣且实用的Python实例
2024-11-19 07:32:35 +0800 CST
GROMACS:一个美轮美奂的C++库
2024-11-18 19:43:29 +0800 CST
Python实现Zip文件的暴力破解
2024-11-19 03:48:35 +0800 CST
WebSQL数据库:HTML5的非标准伴侣
2024-11-18 22:44:20 +0800 CST
API 管理系统售卖系统
2024-11-19 08:54:18 +0800 CST
淘宝npm镜像使用方法
2024-11-18 23:50:48 +0800 CST
随机分数html
2025-01-25 10:56:34 +0800 CST
PHP设计模式:单例模式
2024-11-18 18:31:43 +0800 CST
赚点点任务系统
2024-11-19 02:17:29 +0800 CST
Vue3中如何进行性能优化?
2024-11-17 22:52:59 +0800 CST
解决python “No module named pip”
2024-11-18 11:49:18 +0800 CST
使用 sync.Pool 优化 Go 程序性能
2024-11-19 05:56:51 +0800 CST
一个简单的打字机效果的实现
2024-11-19 04:47:27 +0800 CST
Go语言中实现RSA加密与解密
2024-11-18 01:49:30 +0800 CST
Vue中如何使用API发送异步请求?
2024-11-19 10:04:27 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
Vue 3 路由守卫详解与实战
2024-11-17 04:39:17 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
虚拟DOM渲染器的内部机制
2024-11-19 06:49:23 +0800 CST
Vue中的样式绑定是如何实现的?
2024-11-18 10:52:14 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
Nginx 如何防止 DDoS 攻击
2024-11-18 21:51:48 +0800 CST
小技巧vscode去除空格方法
2024-11-17 05:00:30 +0800 CST
PHP如何进行MySQL数据备份?
2024-11-18 20:40:25 +0800 CST
程序员茄子在线接单