讲师中心 微信公众号
AI工具推荐 视频效率加速

Golang channel创建与通信完整示例

秋雪大大_1595

秋雪大大_1595

发布时间:2025-09-10 12:16:01

|

1064人浏览过

|

来源于php中文网

原创

Go的Channel通过通信共享内存,避免数据竞争,简化并发编程。其阻塞机制天然同步,支持无缓冲、有缓冲通信,结合select实现超时、取消等复杂控制,是Go并发的核心。

golang channel创建与通信完整示例

Golang的Channel是其并发模型的核心,提供了一种安全、高效的goroutine间通信机制。简单来说,它就是一个管道,让数据可以在不同的并发执行单元之间流动,极大地简化了并发编程的复杂性。无论是传递数据、同步协程,还是处理复杂的并发模式,Channel都扮演着不可或缺的角色,其设计哲学完美诠释了Go语言“通过通信共享内存,而不是通过共享内存来通信”的理念。

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    fmt.Println("--- Golang Channel 创建与通信示例 ---")

    // 1. 无缓冲通道 (Unbuffered Channel)
    // 创建一个无缓冲的字符串通道。这意味着发送和接收操作会立即阻塞,直到另一端就绪。
    messages := make(chan string)
    var wg sync.WaitGroup // 用于等待goroutine完成

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("[发送者]:准备发送 '你好,无缓冲世界!'")
        messages <- "你好,无缓冲世界!" // 尝试发送,会阻塞直到main goroutine准备好接收
        fmt.Println("[发送者]:'你好,无缓冲世界!' 已发送。")
    }()

    fmt.Println("[接收者]:准备接收无缓冲消息...")
    msg := <-messages // 尝试接收,会阻塞直到发送者发送消息
    fmt.Printf("[接收者]:收到无缓冲消息: '%s'\n", msg)
    fmt.Println("[接收者]:无缓冲消息处理完毕。")
    wg.Wait() // 等待发送者goroutine完成,确保输出顺序

    fmt.Println("\n---")

    // 2. 有缓冲通道 (Buffered Channel)
    // 创建一个容量为2的整数缓冲通道。在容量未满时,发送操作不会阻塞;在容量未空时,接收操作不会阻塞。
    bufferedCh := make(chan int, 2)

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("[缓冲发送者]:发送 1")
        bufferedCh <- 1 // 不会阻塞,通道有空位
        fmt.Println("[缓冲发送者]:发送 2")
        bufferedCh <- 2 // 不会阻塞,通道仍有空位

        fmt.Println("[缓冲发送者]:尝试发送 3 (通道已满,将阻塞直到有接收者)")
        bufferedCh <- 3 // 阻塞,因为通道已满
        fmt.Println("[缓冲发送者]:3 已发送。")
        close(bufferedCh) // 发送完毕,关闭通道
    }()

    // 给发送goroutine一点时间启动并填充缓冲
    time.Sleep(50 * time.Millisecond)

    fmt.Println("[缓冲接收者]:准备接收缓冲消息...")
    val1 := <-bufferedCh
    fmt.Printf("[缓冲接收者]:收到缓冲消息: %d\n", val1)

    val2 := <-bufferedCh
    fmt.Printf("[缓冲接收者]:收到缓冲消息: %d\n", val2)

    // 此时通道有一个空位,发送者goroutine中的 `bufferedCh <- 3` 将不再阻塞,可以继续发送
    val3 := <-bufferedCh
    fmt.Printf("[缓冲接收者]:收到缓冲消息: %d\n", val3)

    // 使用 for-range 循环接收通道中所有剩余的值,直到通道被关闭
    fmt.Println("[缓冲接收者]:开始使用 for-range 接收剩余消息...")
    for v := range bufferedCh {
        fmt.Printf("[缓冲接收者]:通过 for-range 收到: %d\n", v)
    }
    fmt.Println("[缓冲接收者]:所有缓冲消息接收完毕。")
    wg.Wait() // 等待缓冲发送者goroutine完成

    fmt.Println("\n---")

    // 3. Select 语句与通道超时
    // select 允许一个goroutine等待多个通信操作。它会阻塞直到某个case可以执行。
    timeoutCh := make(chan string)
    doneCh := make(chan bool)

    go func() {
        time.Sleep(2 * time.Second) // 模拟一个耗时操作
        timeoutCh <- "操作完成!"
        // close(timeoutCh) // 通常这里不会关闭,因为可能还有其他操作
    }()

    go func() {
        time.Sleep(3 * time.Second) // 模拟一个更耗时,或者说,完成信号
        doneCh <- true
    }()

    fmt.Println("[Select示例]:开始等待操作或超时...")
    select {
    case res := <-timeoutCh:
        fmt.Printf("[Select示例]:收到结果: '%s'\n", res)
    case <-time.After(1 * time.Second): // 1秒后超时
        fmt.Println("[Select示例]:操作超时了!")
    }

    // 再次使用select,这次等待done信号
    fmt.Println("[Select示例]:等待最终完成信号...")
    select {
    case res := <-timeoutCh: // 如果之前没收到,这里可能还会收到
        fmt.Printf("[Select示例]:再次收到结果: '%s'\n", res)
    case <-doneCh:
        fmt.Println("[Select示例]:收到完成信号,程序即将退出。")
    case <-time.After(5 * time.Second): // 再次设置一个超时,防止死锁
        fmt.Println("[Select示例]:等待完成信号超时。")
    }

    fmt.Println("\n所有示例运行完毕。")
}

为什么Go的Channel是并发编程的利器?

我个人觉得,Go语言的Channel之所以能成为并发编程的“杀手锏”,核心在于它对“共享内存通过通信”这一理念的彻底贯彻。传统多线程编程中,我们常常为了保护共享数据而绞尽脑汁地使用互斥锁(Mutex)、信号量(Semaphore)等机制。这不仅代码写起来复杂,更可怕的是,一不小心就可能引入死锁、竞态条件等难以调试的bug。每次看到那些复杂的锁机制,我都会感到一种深深的疲惫。

Channel则提供了一种完全不同的思路。它不是让你去“保护”共享数据,而是让你去“传递”数据。数据在goroutine之间流动,每次只有一个goroutine拥有它的所有权,这从根本上避免了数据竞争。你可以把它想象成一个安全的邮筒,你把信(数据)投进去,然后另一个邮递员(goroutine)来取走。这个过程是天然同步的,你不用担心两个邮递员同时抢一封信,也不用担心信件在传递过程中被篡改。

这种设计哲学,让并发代码变得异常清晰和简洁。我们不再需要过多地关注底层同步原语的复杂性,而是可以专注于业务逻辑本身。Channel的阻塞特性,本身就是一种强大的同步机制:无缓冲Channel的发送和接收必须同时就绪,这天然地形成了一个同步点;有缓冲Channel则提供了一种“解耦”的能力,让发送者和接收者不必完全同步,从而提升了系统的吞吐量。它就像是Go语言为并发编程量身定制的“基础设施”,用起来顺手,而且非常可靠。

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

Channel关闭与安全接收:你真的掌握了吗?

Channel的关闭(

close()
)和安全接收,是使用Channel时一个非常重要但又容易被忽视的细节。很多人可能只是简单地用
<-ch
来接收,或者用
close(ch)
来关闭,但其中隐藏的“坑”可不少。我见过不少因为Channel关闭不当而导致的程序崩溃或者逻辑错误。

首先,

close(ch)
操作的目的是告诉接收者,这个Channel将不再有新的值发送过来。一旦Channel被关闭,就不能再向它发送数据了,否则会引发
panic
。但是,已经发送到Channel中的数据仍然可以被接收。当Channel中所有数据都被接收完毕后,继续从已关闭的Channel接收数据会立即返回零值(对应Channel类型的零值,比如
int
的0,
string
的"")而不会阻塞。

为了安全地接收数据并判断Channel是否已关闭,最推荐的方式是使用

for range
循环:

for value := range myChannel {
    // 处理接收到的值
}
// 当myChannel被关闭且所有值都已被接收后,循环会自动退出

这种方式简洁且安全,它会一直阻塞直到Channel有新值或者被关闭。

Telegram Channel Operator
Telegram Channel Operator

为 Web3 与 AI 项目管理 Telegram 频道,创作专题帖、公告、摘要、审计报告并制定增长导向的内容日历。

下载

另一种常见的安全接收模式是使用多返回值:

value, ok := <-myChannel
if !ok {
    // Channel已关闭,并且所有值都已接收完毕
    fmt.Println("Channel已关闭")
    return
}
// 正常处理接收到的值

这里的

ok
布尔值会告诉你接收操作是否成功。如果
ok
为
false
,说明Channel已关闭且没有更多数据了。

关于关闭Channel,有一个非常重要的原则:通常由发送者关闭Channel,而不是接收者。这是因为发送者知道何时没有更多的数据需要发送。如果接收者关闭Channel,可能会在发送者仍然尝试发送数据时引发

panic
。而且,尝试关闭一个已经关闭的Channel也会引发
panic
。所以,在设计并发模式时,务必明确谁负责关闭Channel,并且确保只关闭一次。在有多个发送者的情况下,通常会引入一个
sync.WaitGroup
或者一个单独的
done
Channel来协调关闭操作,确保所有发送者都完成任务后,再由某个协调者来关闭主数据Channel。

Channel在复杂并发场景下的实战应用

Channel不仅仅是用来传递几个简单的值,它在构建复杂的并发系统时,简直是Go语言提供的一把瑞士军刀。我个人在项目实践中,经常会用到以下几种模式,它们能大大提升代码的健壮性和可维护性:

  1. 工作池(Worker Pool)模式: 想象一下,你有一堆任务需要处理,但又不想启动过多的goroutine导致资源耗尽。这时候,Channel就能派上用场。你可以创建一个任务Channel,让多个工作goroutine从这个Channel中接收任务并执行。当所有任务都发送到Channel后,关闭任务Channel,工作goroutine就会在处理完所有任务后优雅地退出。这就像一个工厂,流水线上不断有产品(任务)过来,工人们(goroutine)各自去取产品加工。

    // 概念示例
    tasks := make(chan int, 100)
    results := make(chan int, 100)
    
    // 启动3个worker
    for i := 0; i < 3; i++ {
        go func() {
            for task := range tasks {
                // 处理任务...
                results <- task * 2 // 将结果发送到结果Channel
            }
        }()
    }
    
    // 发送任务
    for i := 0; i < 10; i++ {
        tasks <- i
    }
    close(tasks) // 所有任务发送完毕
    
    // 接收结果 (这里需要一个机制来知道何时所有结果都已处理)
    // ...
  2. 扇入/扇出(Fan-in/Fan-out)模式: 当你有多个数据源需要合并到一个Channel(扇入),或者一个数据源需要分发给多个处理者(扇出)时,Channel提供了非常直观的解决方案。扇出通常很简单,多个goroutine同时从一个Channel接收数据即可。扇入则需要一个聚合器,它从多个输入Channel接收数据,然后发送到一个统一的输出Channel。这在数据处理管道中非常常见,比如从多个API拉取数据,然后统一处理。

  3. 超时与取消(Timeout and Cancellation):

    select
    语句结合
    time.After
    或
    context.Done()
    Channel,是处理并发操作超时和取消的黄金搭档。当一个操作可能耗时过长,或者你希望在特定条件下提前终止它时,
    select
    可以监听多个Channel,包括你的操作结果Channel和超时/取消信号Channel。哪个Channel先就绪,就执行对应的逻辑。

    // 概念示例:结合Context实现取消
    ctx, cancel := context.WithCancel(context.Background())
    dataCh := make(chan string)
    
    go func(ctx context.Context) {
        select {
        case <-time.After(5 * time.Second):
            dataCh <- "数据已生成"
        case <-ctx.Done():
            fmt.Println("操作被取消")
            return
        }
    }(ctx)
    
    // 模拟在某个时刻取消操作
    go func() {
        time.Sleep(2 * time.Second)
        cancel() // 取消操作
    }()
    
    select {
    case data := <-dataCh:
        fmt.Println("接收到数据:", data)
    case <-ctx.Done():
        fmt.Println("主goroutine检测到操作被取消")
    }
  4. 请求-响应(Request-Response)模式: 有时一个goroutine需要向另一个goroutine发送一个请求,并等待其响应。虽然直接传递数据Channel可以实现,但更优雅的方式是在请求中嵌入一个临时的响应Channel。

    type Request struct {
        data    string
        respCh  chan string // 用于接收响应的Channel
    }
    
    reqCh := make(chan Request)
    
    go func() { // 处理器goroutine
        for req := range reqCh {
            // 处理请求...
            req.respCh <- "处理结果:" + req.data // 将结果发送回请求者
        }
    }()
    
    // 请求者goroutine
    respCh := make(chan string)
    reqCh <- Request{data: "我的请求", respCh: respCh}
    response := <-respCh
    fmt.Println(response)

这些模式只是冰山一角。Channel的灵活性和Go语言的并发原语结合,可以构建出几乎任何你想要的并发模型。关键在于理解其核心原理,并善用Go提供的工具。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

519

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

616

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1196

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

791

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1766

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

4994

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

2314

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

4186

2025.06.17

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

40

2026.10.28

热门下载

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

精品课程

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn