【Go语言学习系列30】并发编程(三):select语句

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第30篇,当前位于第三阶段(进阶篇)

🚀 第三阶段:进阶篇
  1. 并发编程(一):goroutine基础
  2. 并发编程(二):channel基础
  3. 并发编程(三):select语句 👈 当前位置
  4. 并发编程(四):sync包
  5. 并发编程(五):并发模式
  6. 并发编程(六):原子操作与内存模型
  7. 数据库编程(一):SQL接口
  8. 数据库编程(二):ORM技术
  9. Web开发(一):路由与中间件
  10. Web开发(二):模板与静态资源
  11. Web开发(三):API开发
  12. Web开发(四):认证与授权
  13. Web开发(五):WebSocket
  14. 微服务(一):基础概念
  15. 微服务(二):gRPC入门
  16. 日志与监控
  17. 第三阶段项目实战:微服务聊天应用

📚 查看完整Go语言学习系列导航

📖 文章导读

在本文中,您将了解:

  • select语句的基本语法和工作原理
  • 如何使用select实现超时处理和非阻塞IO
  • 多个channel操作的同步处理
  • select在并发编程中的常见应用模式
  • 使用select避免goroutine泄漏和死锁的技巧

作为Go并发编程的核心组件之一,select语句让我们能够优雅地处理多个channel操作,实现超时控制、取消操作和非阻塞通信等关键功能。掌握select是构建健壮Go并发程序的必备技能。

Go select语句示意图

并发编程(三):select语句

在前两篇文章中,我们深入探讨了Go语言并发编程的基础:goroutine和channel。今天,我们将介绍Go并发编程的另一个强大工具:select语句。select语句允许一个goroutine等待多个通信操作,是Go语言中处理多channel操作的关键机制。

一、select基础

1.1 基本语法

select语句的语法类似于switch语句,但它的作用完全不同。select语句用于在多个发送/接收channel操作中进行选择,语法如下:

select {
   
   
case <-ch1:
    // 如果从ch1成功接收数据,则执行此分支
case ch2 <- value:
    // 如果成功向ch2发送数据,则执行此分支
case x := <-ch3:
    // 如果从ch3成功接收数据,则执行此分支,并将接收的值赋给x
default:
    // 如果上面的case都没有准备好,则执行此分支(可选)
}

1.2 工作原理

select语句的工作原理如下:

  1. 评估所有channel表达式:首先计算所有case中的channel表达式
  2. 评估所有发送/接收操作:尝试在所有channel上执行发送或接收操作
  3. 阻塞或执行
    • 如果有一个或多个case准备好(可发送或可接收),Go会随机选择一个执行
    • 如果没有case准备好且有default分支,则执行default分支
    • 如果没有case准备好且没有default分支,则select语句阻塞,直到某个case准备好

1.3 特性与规则

select语句有几个重要的特性:

  1. 随机选择:当多个case同时准备好时,select会随机选择一个执行,这避免了固定顺序可能导致的饥饿问题
  2. 零case:空的select语句(select{})会永远阻塞
  3. 无匹配死锁检测:如果select语句中没有default分支,且所有case都阻塞,则当前goroutine会被阻塞;如果所有goroutine都被阻塞,Go运行时会检测到死锁并报错
  4. default避免阻塞:包含default分支的select语句永远不会阻塞

1.4 基本示例

下面是一个简单的select示例,展示了如何在多个channel上等待:

package main

import (
    "fmt"
    "time"
)

func main() {
   
   
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    // 在两个不同的goroutine中发送数据
    go func() {
   
   
        time.Sleep(1 * time.Second)
        ch1 <- "from channel 1"
    }()
    
    go func() {
   
   
        time.Sleep(2 * time.Second)
        ch2 <- "from channel 2"
    }()
    
    // 使用select等待两个channel
    for i := 0; i < 2; i++ {
   
   
        select {
   
   
        case msg1 := <-ch1:
            fmt.Println("Received", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received", msg2)
        }
    }
}

在这个例子中,select语句会等待ch1或ch2有数据可接收。由于ch1会在1秒后接收到数据,而ch2在2秒后接收到数据,所以先打印"Received from channel 1",然后打印"Received from channel 2"。

二、超时处理

select语句结合time.After函数可以很方便地实现超时处理,这是Go并发编程中的一个常见模式。

2.1 基本超时模式

以下是一个基本的超时模式示例:

package main

import (
    "fmt"
    "time"
)

func main() {
   
   
    ch := make(chan string)
    
    go func() {
   
   
        // 模拟耗时操作
        time.Sleep(2 * time.Second)
        ch <- "操作完成"
    }()
    
    select {
   
   
    case result := <-ch:
        fmt.Println("成功:", result)
    case <-time.After(1 * time.Second):
        fmt.Println("操作超时")
    }
}

在这个例子中,如果在1秒内没有从ch接收到数据,则会执行超时分支。由于操作需要2秒,所以会打印"操作超时"。

2.2 超时重试模式

结合循环和超时,可以实现带重试的超时模式:

package main

import (
    "fmt"
    "time"
)

func main() {
   
   
    ch := make(chan string)
    
    go func() {
   
   
        // 模拟需要多次尝试才能成功的操作
        time.Sleep(2500 * time.Millisecond)
        ch <- "操作成功"
    }()
    
    timeout := 1 * time.Second
    maxRetries := 3
    
    for retry := 0; retry < maxRetries; retry++ {
   
   
        select {
   
   
        case result := <-ch:
            fmt.Println("成功:", result)
            return
        case <-time.After(timeout):
            fmt.Printf("尝试 %d 超时,重试中...\n", retry+1)
        }
    }
    
    fmt.Println("达到最大重试次数,操作失败"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值