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

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

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

推荐文章

php内置函数除法取整和取余数
2024-11-19 10:11:51 +0800 CST
如何将TypeScript与Vue3结合使用
2024-11-19 01:47:20 +0800 CST
Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
Vue3中的自定义指令有哪些变化?
2024-11-18 07:48:06 +0800 CST
利用Python构建语音助手
2024-11-19 04:24:50 +0800 CST
mysql 优化指南
2024-11-18 21:01:24 +0800 CST
LLM驱动的强大网络爬虫工具
2024-11-19 07:37:07 +0800 CST
html文本加载动画
2024-11-19 06:24:21 +0800 CST
Nginx 负载均衡
2024-11-19 10:03:14 +0800 CST
一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
小技巧vscode去除空格方法
2024-11-17 05:00:30 +0800 CST
一个有趣的进度条
2024-11-19 09:56:04 +0800 CST
如何在Vue3中定义一个组件?
2024-11-17 04:15:09 +0800 CST
解决 PHP 中的 HTTP 请求超时问题
2024-11-19 09:10:35 +0800 CST
JS中 `sleep` 方法的实现
2024-11-19 08:10:32 +0800 CST
程序员茄子在线接单