0

0

Go语言中 select 语句的奇怪行为:协程调度与时间片问题

心靈之曲

心靈之曲

发布时间:2025-09-19 17:18:01

|

682人浏览过

|

来源于php中文网

原创

go语言中 select 语句的奇怪行为:协程调度与时间片问题

本文旨在解释 Go 语言中 select 语句在并发场景下可能出现的“奇怪”行为,特别是当与 time.Ticker 结合使用时。通过分析一个简单的示例,我们将深入探讨 Go 语言的协程调度机制,以及如何避免因 CPU 密集型循环而导致的协程饥饿问题。本文将提供详细的解释和解决方案,帮助开发者更好地理解和使用 select 语句。

在 Go 语言中,select 语句用于在多个通道操作中进行选择。当多个通道同时准备好时,select 会随机选择一个执行。然而,在某些情况下,select 的行为可能会让人感到困惑,尤其是在涉及时间控制和并发操作时。

考虑以下示例:

package main

import (
    "fmt"
    "time"
    "runtime"
)

func main() {
    rt := time.NewTicker(time.Second / 60)
    defer rt.Stop()

    for {
        select {
        case <-rt.C:
            fmt.Println("time")
        default:
            // runtime.Gosched() // 取消注释此行可以解决问题
        }
        // time.Sleep(1 * time.Millisecond) // 加上这行也可以解决问题
    }
}

这段代码的目的是每 1/60 秒打印一次 "time"。然而,如果没有 time.Sleep 或 runtime.Gosched(),你可能会发现 "time" 很少甚至从不打印。

立即学习go语言免费学习笔记(深入)”;

问题分析:协程调度和 CPU 密集型循环

问题在于 for 循环是一个 CPU 密集型循环,它会不断地检查 select 语句。由于 select 语句中有一个 default 分支,如果 rt.C 通道没有数据,select 会立即执行 default 分支。这导致循环快速执行,而 Go 语言的协程调度器没有机会将 CPU 时间分配给运行 time.Ticker 的协程。

换句话说,main 函数所在的协程一直在运行,而 time.Ticker 所在的协程没有机会发送数据到 rt.C 通道。这被称为协程饥饿。

解决方案

有几种方法可以解决这个问题:

玫瑰克隆工具
玫瑰克隆工具

AI图文笔记一键生成创作并自动发布助手

下载
  1. time.Sleep(): 在 default 分支中添加 time.Sleep() 可以强制当前协程让出 CPU 时间,让其他协程有机会运行。

    default:
        time.Sleep(1 * time.Millisecond)

    通过休眠一小段时间,time.Ticker 协程就有机会发送数据到 rt.C 通道。

  2. runtime.Gosched(): 调用 runtime.Gosched() 可以显式地将当前协程从运行状态切换到等待状态,允许其他协程运行。

    default:
        runtime.Gosched()

    runtime.Gosched() 比 time.Sleep() 更轻量级,因为它不会阻塞协程,只是让它暂时让出 CPU 时间。

选择哪种方案?

  • time.Sleep() 简单易懂,但可能会引入不必要的延迟。
  • runtime.Gosched() 更有效率,因为它只在需要时才让出 CPU 时间。

在实际应用中,选择哪种方案取决于具体的需求。如果需要精确的时间控制,time.Sleep() 可能更适合。如果性能是关键,runtime.Gosched() 可能是更好的选择。

总结与注意事项

在使用 select 语句时,需要注意以下几点:

  • 避免 CPU 密集型循环,这可能会导致协程饥饿。
  • 使用 time.Sleep() 或 runtime.Gosched() 让出 CPU 时间,让其他协程有机会运行。
  • 理解 Go 语言的协程调度机制,这有助于编写高效的并发程序。
  • 确保 time.Ticker 实例在使用完毕后调用 Stop() 方法,防止资源泄漏。

通过理解 select 语句的行为和 Go 语言的协程调度机制,你可以避免潜在的问题,编写更健壮、更高效的并发程序。

相关专题

更多
Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

233

2023.09.06

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

444

2023.09.25

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

246

2023.10.13

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

693

2023.10.26

Go语言实现运算符重载有哪些方法
Go语言实现运算符重载有哪些方法

Go语言不支持运算符重载,但可以通过一些方法来模拟运算符重载的效果。使用函数重载来模拟运算符重载,可以为不同的类型定义不同的函数,以实现类似运算符重载的效果,通过函数重载,可以为不同的类型实现不同的操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

191

2024.02.23

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2024.02.23

go语言开发工具大全
go语言开发工具大全

本专题整合了go语言开发工具大全,想了解更多相关详细内容,请阅读下面的文章。

280

2025.06.11

go语言引用传递
go语言引用传递

本专题整合了go语言引用传递机制,想了解更多相关内容,请阅读专题下面的文章。

158

2025.06.26

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

3

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.7万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号