0

0

深入理解Go语言并发:通道复用与非确定性行为的观察

DDD

DDD

发布时间:2025-10-10 13:19:45

|

410人浏览过

|

来源于php中文网

原创

深入理解Go语言并发:通道复用与非确定性行为的观察

本文深入探讨Go语言中基于goroutine和channel实现的并发模式,特别是如何通过通道复用(fan-in)聚合多个并发源。通过分析一个常见的“锁步”现象案例,我们揭示了并发程序非确定性的本质,并强调了在观察异步行为时,需要足够的执行时间来充分展现随机延迟的效果,从而避免对并发机制产生误解。

Go语言并发基础:Goroutine与Channel

go语言以其内置的并发原语——goroutine和channel——极大地简化了并发编程。goroutine是轻量级的执行线程,由go运行时管理,而channel则是goroutine之间进行通信和同步的管道。这种“通过通信共享内存而非通过共享内存通信”的设计哲学,使得并发程序的编写更加安全和直观。

通道复用(Fan-In)模式

在复杂的并发场景中,我们经常需要从多个独立的并发源收集数据并将其聚合到一个单一的通道中进行处理。这种模式被称为“通道复用”(Fan-In)。fanIn函数是实现这一模式的典型示例,它接收多个输入通道,并启动独立的goroutine将每个输入通道的数据转发到一个新的输出通道。

考虑以下boring函数,它模拟了一个持续发送消息的并发源,每个消息之间伴随随机延迟:

package main

import (
    "fmt"
    "time"
    "math/rand"
)

// boring函数模拟一个并发消息生产者
func boring(msg string) <-chan string {
    c := make(chan string)
    go func() { // 启动一个goroutine发送消息
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) // 随机延迟
        }
    }()
    return c
}

// fanIn函数将两个输入通道的消息复用到一个输出通道
func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() { for { c <- <-input1 } }() // 从input1读取并转发
    go func() { for { c <- <-input2 } }() // 从input2读取并转发
    return c
}

在main函数中,我们创建两个boring实例("Joe"和"Ann"),并通过fanIn函数将它们的输出聚合。然后,我们从聚合通道中读取消息:

func main() {
    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 10; i++ { // 尝试读取10条消息
        fmt.Println(<-c)
    }
    fmt.Printf("You're both boring, I'm leaving...\n")
}

观察到的“锁步”现象与并发的非确定性

当运行上述代码时,我们可能会观察到以下输出:

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

Joe 0
Ann 0
Joe 1
Ann 1
Joe 2
Ann 2
Joe 3
Ann 3
Joe 4
Ann 4
You're both boring, I'm leaving...

这种现象被称为“锁步”(lock-step),即尽管我们期望"Joe"和"Ann"的消息能够异步交错出现,但它们却似乎同步地一对一对出现。这可能会让人误以为并发机制没有按预期工作。

然而,这里的关键在于并发的非确定性。Go调度器在多个goroutine之间切换,并且time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)引入了随机延迟。在程序启动初期,或者当读取消息的次数较少时(例如本例中的10次),即使存在随机延迟,也可能因为Go调度器的行为、系统负载或随机数生成器初期的值,导致两个goroutine在短时间内表现出近似同步的行为。

Designify
Designify

拖入图片便可自动去除背景✨

下载

并发性意味着可以独立执行,但不保证执行顺序或精确的时间交错。在短时间内,两个独立的goroutine可能恰好以相似的节奏生成并发送消息,尤其是在随机延迟的范围允许这种“巧合”发生时。

揭示异步行为:增加观察时间

要真正观察到并发带来的异步和非确定性,我们需要给程序足够的运行时间,让随机延迟的效果充分累积并显现出来。只需简单地增加从聚合通道读取消息的次数,例如从10次增加到20次:

func main() {
    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 20; i++ { // 增加读取次数,例如到20次
        fmt.Println(<-c)
    }
    fmt.Printf("You're both boring, I'm leaving...\n")
}

重新运行程序,我们更有可能看到以下类型的输出,其中消息不再严格地一对一出现,而是展现出明显的交错和异步性:

Joe 0
Ann 0
Joe 1
Ann 1
Joe 2
Ann 2
Joe 3
Ann 3
Joe 4
Ann 4
Joe 5
Ann 5
Joe 6
Ann 6
Ann 7  // Ann的消息提前了
Joe 7
Joe 8
Joe 9
Ann 8
Ann 9  // Ann的消息滞后了

这个输出清晰地表明,"Ann"和"Joe"的消息不再严格同步,而是根据它们各自的随机延迟在聚合通道中交错。

总结与注意事项

  1. 并发不等于并行或严格交错: Go的并发模型允许独立执行的goroutine,但它们的实际执行顺序和时间交错是运行时调度器决定的,通常是非确定性的。
  2. 随机性需要时间来体现: 当程序中引入随机延迟时,需要足够的运行时间或数据量才能充分展现这种随机性对执行顺序的影响。短时间的观察可能无法捕捉到异步行为的全貌。
  3. 通道复用是强大的模式: fanIn模式是处理多个并发源的优雅方式,它将复杂性封装在内部,对外提供一个统一的接口。
  4. 调试并发程序: 调试并发问题时,要意识到输出可能因运行环境和调度策略而异。增加日志、使用time.Sleep(在测试中模拟延迟)或Go的testing包进行并发测试是常用的方法。

理解Go语言并发的非确定性是掌握其强大功能的基础。通过耐心观察和适当的测试,我们可以更好地理解和利用goroutine和channel来构建高效、健壮的并发应用程序。

相关专题

更多
硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

991

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

51

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

232

2025.12.29

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

472

2023.08.10

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

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

233

2023.09.06

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

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

442

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语言相关的教程以及文章,欢迎大家前来学习。

691

2023.10.26

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

74

2025.12.31

热门下载

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

精品课程

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

共32课时 | 3.2万人学习

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号