0

0

Go 调度器奇偶行为探究:并发程序中的非确定性与同步机制

DDD

DDD

发布时间:2025-10-16 11:05:13

|

316人浏览过

|

来源于php中文网

原创

go 调度器奇偶行为探究:并发程序中的非确定性与同步机制

本文深入探讨了 Go 语言并发程序中一个有趣的现象:当循环次数为奇数时,程序能够完整输出所有数值;而当循环次数为偶数时,最后一个数值却丢失。通过分析代码示例和调度器行为,揭示了并发程序中非确定性的本质,并强调了使用同步机制确保 Goroutine 完成的重要性。文章将帮助读者理解 Go 调度器的工作方式,并掌握编写可靠并发程序的关键技巧。

在 Go 语言中,并发编程是一项强大的特性,它允许程序同时执行多个任务。然而,并发也引入了非确定性,这意味着程序的行为可能因运行环境、调度器的决策等因素而异。本文将通过一个具体的例子,深入探讨 Go 调度器在并发程序中的行为,并解释为何循环次数的奇偶性会影响程序的输出结果。

问题描述

考虑以下 Go 代码:

package main

import "runtime"

func main() {
    c2 := make(chan int)

    go func() {
        for v := range c2 {
            println("c2 =", v, "numof routines:", runtime.NumGoroutine())
        }
    }()

    for i := 1; i <= 10001; i++ {
        c2 <- i
        // runtime.Gosched()
    }
}

这段代码创建了一个 Goroutine,它从通道 c2 中接收整数并打印。主 Goroutine 向 c2 发送从 1 到 10001(或 10000)的整数。

令人惊讶的是,当循环次数为奇数(例如 10001)时,程序能够完整输出所有数值。但是,当循环次数为偶数(例如 10000)时,程序会丢失最后一个数值。

原因分析:调度器的非确定性

这种现象的根本原因是 Go 调度器的非确定性。当 main 函数返回时,程序会终止,而不会等待任何 Goroutine 完成。因此,Goroutine 是否能在 main 函数返回之前完成所有工作,取决于调度器的调度策略以及一些外部因素。

循环次数的奇偶性可能只是影响调度的一个因素。当循环次数为偶数时,可能由于某种巧合,调度器在 Goroutine 处理完所有数据之前就切换回了 main Goroutine,导致 main 函数提前返回,从而导致最后一个数值丢失。

FreeTTS
FreeTTS

FreeTTS是一个免费开源的在线文本到语音生成解决方案,可以将文本转换成MP3,

下载

解决方案:使用同步机制

为了确保 Goroutine 在 main 函数返回之前完成所有工作,我们需要使用同步机制。Go 提供了多种同步机制,例如 sync.WaitGroup。

以下是如何使用 sync.WaitGroup 修改代码以确保所有数值都被处理:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    c2 := make(chan int)
    var wg sync.WaitGroup

    wg.Add(1) // 增加计数器,表示有一个 Goroutine 需要等待

    go func() {
        defer wg.Done() // Goroutine 完成时减少计数器
        for v := range c2 {
            fmt.Println("c2 =", v, "numof routines:", runtime.NumGoroutine())
        }
    }()

    for i := 1; i <= 10000; i++ {
        c2 <- i
        // runtime.Gosched()
    }
    close(c2) // 关闭通道,通知 Goroutine 没有更多数据了

    wg.Wait() // 等待计数器归零,表示所有 Goroutine 都已完成
}

在这个修改后的代码中,我们使用 sync.WaitGroup 来等待 Goroutine 完成。

  1. wg.Add(1) 增加计数器,表示有一个 Goroutine 需要等待。
  2. defer wg.Done() 在 Goroutine 完成时减少计数器。
  3. close(c2) 关闭通道,通知 Goroutine 没有更多数据了。这是很关键的一步,否则goroutine会一直阻塞等待通道的数据。
  4. wg.Wait() 等待计数器归零,表示所有 Goroutine 都已完成。

通过使用 sync.WaitGroup,我们可以确保 main 函数在所有 Goroutine 完成工作后才返回,从而避免了数据丢失的问题。

总结

Go 调度器的非确定性是并发编程中需要注意的一个重要问题。为了编写可靠的并发程序,我们需要使用适当的同步机制来确保 Goroutine 在程序退出之前完成所有工作。sync.WaitGroup 是一个常用的同步机制,可以用于等待一组 Goroutine 完成。此外,使用 close 关闭channel也是通知goroutine不再有数据输入的重要手段。理解这些概念并正确应用它们,可以帮助我们编写出更加健壮和可靠的 Go 并发程序。

相关专题

更多
Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

247

2025.11.14

golang channel相关教程
golang channel相关教程

本专题整合了golang处理channel相关教程,阅读专题下面的文章了解更多详细内容。

342

2025.11.17

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

html编辑相关教程合集
html编辑相关教程合集

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

56

2026.01.21

三角洲入口地址合集
三角洲入口地址合集

本专题整合了三角洲入口地址合集,阅读专题下面的文章了解更多详细内容。

50

2026.01.21

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

396

2026.01.21

妖精漫画入口地址合集
妖精漫画入口地址合集

本专题整合了妖精漫画入口地址合集,阅读专题下面的文章了解更多详细内容。

118

2026.01.21

java版本选择建议
java版本选择建议

本专题整合了java版本相关合集,阅读专题下面的文章了解更多详细内容。

3

2026.01.21

Java编译相关教程合集
Java编译相关教程合集

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

16

2026.01.21

热门下载

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

精品课程

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

共32课时 | 4.1万人学习

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号