尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
go并发的核心数据结构--channel
开篇go并发的核心数据结构–channelgo语言并发的核心哲学是不要通过共享内存来通信而要通过通信来共享内存。channel通道就是go语言提供的协程间的通信方式用来传递数据、同步状态、协调并发从底层避免了共享内存带来的竞态问题是 Go 原生并发编程的标准方案。一、channel 基础语法1. 声明与创建仅声明nil通道读写都会永久阻塞varchchanint创建无缓冲通道(同步)ch:make(chanint)创建有缓冲通道(异步)先入先出ch:make(chanint,5)// 容量为5的有缓冲int通道2. 三大核心操作操作语法说明发送ch - value将数据写入通道接收value : -ch从通道读取数据关闭close(ch)关闭通道补充接收操作支持双返回值写法 value, ok : -chok 为 false 表示通道已关闭且缓冲区已空。二、核心分类无缓冲通道 vs 有缓冲通道这是 channel 最核心的分类两者的行为模式和适用场景完全不同。1. 无缓冲通道同步通道定义顾名思义无缓冲就是数据在经过通道时不能停止此通道创建时没有容量make(chan int)核心特性简单来说发送和接收的操作是同步完成一方没有就绪另一方就会阻塞。适用场景goroutine 协程之间的同步等待、一对一即时通信。下面看一个具体例子用无缓冲通道实现主 goroutine 等待子 goroutine 完成任务funcmain(){done:make(chanstruct{})// 空结构体不占用内存专用于传递信号gofunc(){// 子goroutine执行业务逻辑fmt.Println(子goroutine任务执行中...)time.Sleep(1*time.Second)fmt.Println(子goroutine任务完成)done-struct{}{}// 发送完成信号}()-done// 阻塞等待直到收到信号fmt.Println(主goroutine收到完成信号继续执行)}2. 有缓冲通道异步通道定义顾名思义有缓冲就是数据在经过通道时可以停留等待此通道创建时指定的缓冲区容量决定可以停留的数据个数make(chan int, 5)核心特性缓冲区空时可发送不可接收接收会阻塞缓冲区未满时可发送可接收不会阻塞缓冲区满时不可发送可接收发送会阻塞。适用场景生产者消费者解耦、并发限流、异步任务队列。下面看一个具体例子简单生产者消费者模型funcmain(){ch:make(chanint,3)// 容量为3的缓冲通道// 生产者发送数据gofunc(){fori:1;i5;i{ch-i fmt.Printf(生产者发送数据%d\n,i)}close(ch)// 发送完毕关闭通道}()// 消费者接收数据fornum:rangech{// for range 会持续监听ch管道直到ch管道关闭且缓冲区为空fmt.Printf(消费者收到数据%d\n,num)}fmt.Println(所有数据处理完成)}3. 单向通道补充一点Go 语言中默认的通道是双向通道可以进行发送和接收操作而为了提升代码安全性Go 支持单向通道限制通道在函数参数中只能发送或只能接收chan- T只写通道只能发送数据不能接收-chan T只读通道只能接收数据不能发送// 生产者函数参数为只写通道funcproducer(chchan-int){fori:0;i5;i{ch-i}close(ch)}// 消费者函数参数为只读通道funcconsumer(ch-chanint){fornum:rangech{fmt.Println(num)}}三、select 多路并发控制前面讲解了channle 通道的基本使用方法在这里会展示当需要同时监听多个通道的状态比如同时等待多个任务结果、处理多路信号时使用select可以同时监听多个通道的读写事件哪个通道就绪就执行对应分支是 Go 并发控制的核心语法。funcmain(){// 使用select case多路并发控制ch1:make(chanint)ch2:make(chanint)select{casech1-1:fmt.Println(ch1可写)caseval:-ch2:fmt.Println(ch2可读,val)default:fmt.Println(其它情况)}}官方规范就绪即执行哪个 case 的通道操作不阻塞就绪就执行对应分支。随机选择多个 case 同时就绪时随机选择一个执行不会按顺序优先匹配。default 非阻塞存在 default 分支时所有通道都阻塞则执行 default不会挂起 goroutine没有 default 则整体阻塞。nil 通道忽略nil 通道对应的 case 会永远阻塞相当于被排除在监听之外。禁用 fallthrough每个 case 独立不能穿透。小结channel 规范内置函数 len() 和 cap() 可以作用于通道分别查询缓冲区中数据的个数、缓冲区的大小。通道实现一种 FIFO先入先出的队列数据总是按照写入的顺序流出通道。goroutine 泄漏goroutine 因为永久阻塞无法退出占用的栈内存等资源无法释放长期运行会耗尽系统资源。1. 阻塞向 nil 通道发送或接收数据永久阻塞。无缓冲通道没有配对的接收 / 发送操作对应端永久阻塞。有缓冲通道缓冲区空接收端阻塞缓冲区满发送端阻塞。2. 通道关闭关闭操作只能由发送方执行接收方不应关闭通道。重复关闭已关闭的通道、关闭nil通道都会触发panic向已关闭的通道发送数据触发panic。向已关闭的通道接收数据如缓冲区有数据则正常读取无则返回对应类型零值双返回值的 ok 为false。四、实现原理1. 数据结构源码包中定义了通道的数据结构channel 的底层是 hchan 结构体typehchanstruct{qcountuint// 当前队列元素数量dataqsizuint// 环形缓冲区总容量make(chan T, N) 的Nbuf unsafe.Pointer// 环形缓冲区数组起始地址仅带缓冲chan才有elemsizeuint16// 单个元素占用字节大小closeduint16// 是否关闭0未关闭1已关闭elemtype*_type// 元素类型信息GC、赋值、大小sendxuint// 发送索引下一个存入buf的位置recvxuint// 接收索引下一个取出buf的位置// 等待队列双向链表阻塞的goroutinerecvq waitq// 等待接收的G队列-ch 阻塞的Gsendq waitq// 等待发送的G队列ch-x 阻塞的Glock mutex// 全局互斥锁保护所有字段并发修改}可以看到 hchan 结构体的字段还是比较多的但在这里我们也不是每个字段都需要深入了解。2. 无缓冲通道比如我们创建的通道是无缓冲 Channel(make(chan int))它没有缓冲区因此只需要注意recvq和sendq这两个字段。接着先执行发送操作这时如果没有对应接收操作那么当前协程G就会加入sendq阻塞等待接收 G 唤醒先执行接收操作这时如果没有对应发送操作那么当前协程G就会加入recvq阻塞等待发送 G 唤醒。3. 有缓冲通道而对于有缓冲 Channel(make(chan int, 5))则要复杂一些环形队列chan 内部实现了一个环形队列作为其缓冲区队列的长度dataqsiz是在创建 chan 时指定的。下图展示了一个可以缓存 5 个元素的通道图中的缓冲队列缓存了 qcount 2 个元素dataqsiz 5 代表缓冲队列可以缓存 5 个元素buf 则是指向这个缓冲队列的起始地址sendx 代表下一个写入数据的索引recvx 代表第一个读取数据的索引。写入数据向一个通道中写入数据的简单过程如下如果缓冲区中有空余位置则将数据写入缓冲区结束发送过程如果缓冲区中没有空余位置则将当前协程加入sendq队列进入睡眠并等待被读协程唤醒。但如果缓冲区为空且recvq队列中有协程在等待此时会将数据直接传递给recvq队列中的第一个协程而不必再写入缓冲区。读取数据从一个通道中读取数据的简单过程如下如果缓冲区中有数据则从缓冲区中取出数据结束读取过程如果缓冲区中没有数据则将当前协程加入recvq队列进入睡眠并等待被写协程唤醒。但如果没有缓冲区且sendq队列中有协程在等待此时会直接从sendq队列中的第一个协程中读取数据。关闭通道队列中有协程在等待此时会将数据直接传递给recvq队列中的第一个协程而不必再写入缓冲区。读取数据从一个通道中读取数据的简单过程如下如果缓冲区中有数据则从缓冲区中取出数据结束读取过程如果缓冲区中没有数据则将当前协程加入recvq队列进入睡眠并等待被写协程唤醒。但如果没有缓冲区且sendq队列中有协程在等待此时会直接从sendq队列中的第一个协程中读取数据。关闭通道会把recvq中的协程全部唤醒并返回对应数据的零值把sendq中协程全部唤醒但都会触发panic
RELATED

相关推荐

RC电路充放电时间计算:从原理到实战的电子设计核心技能

RC电路充放电时间计算:从原理到实战的电子设计核心技能

1. 项目概述:从“感觉”到“计算”的必经之路搞硬件、玩单片机、做电源设计,甚至是修个简单的电子小玩意儿,RC电路都是一个绕不开的基础单元。我见过太多新手朋友,包括当年的我自己,在调试一个延时电路或者滤波电路时&…

📅 2026/8/23 1:46:57
Unity多版本共存环境搭建与避坑指南:从环境隔离到高效开发

Unity多版本共存环境搭建与避坑指南:从环境隔离到高效开发

1. 项目概述:为什么Unity多版本共存是开发者的刚需?如果你是一名Unity开发者,无论是独立游戏制作人、技术美术,还是大型项目团队的成员,那么你的电脑里很可能不止一个版本的Unity。这几乎是现代Unity开发工作流的常态。…

📅 2026/8/23 1:46:57
大脑启发的计算:从算法到类器官的旅程

大脑启发的计算:从算法到类器官的旅程

下面把“大脑启发的计算:从算法到类器官”当作一条技术演进链来讲——左端是受神经元/回路启发写出的数学与代码,右端是用真实生物神经元培养出来的“活体计算基质”。中间不是替代关系,而是仿生→混合→生物原生的三级跳。一、算法层&#x…

📅 2026/8/23 1:47:03
MORE NEWS

更多资讯

📰

Streamlit 选择控件选型与实战指南:从 segmented_control 到 multiselect 的正确打开方式

Streamlit 选择控件选型与实战指南:从 segmented_control 到 multiselect 的正确打开方式 【免费下载链接】streamlit Streamlit — A faster way to build and share data apps. 项目地址: https://gitcode.com/gh_mirrors/st/streamlit 选择控件是数据应用…

📰

Druid OPSX 工作流详解:Archive 命令如何安全归档 OpenSpec 中已完成的变更

Druid OPSX 工作流详解:Archive 命令如何安全归档 OpenSpec 中已完成的变更 【免费下载链接】druid 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池 项目地址: https://gitcode.co…

📰

深入解析 Terraform AWS Provider 的 aws_elb 数据源:读取 Classic Load Balancer 配置

深入解析 Terraform AWS Provider 的 aws_elb 数据源:读取 Classic Load Balancer 配置 【免费下载链接】terraform-provider-aws The AWS Provider enables Terraform to manage AWS resources. 项目地址: https://gitcode.com/GitHub_Trending/te/terraform-pro…

📰

ants三种池型怎么选?Pool、PoolWithFunc与泛型PoolWithFuncGeneric实战对比指南

ants三种池型怎么选?Pool、PoolWithFunc与泛型PoolWithFuncGeneric实战对比指南 【免费下载链接】ants 🐜🐜🐜 ants is the most powerful and reliable pooling solution for Go. 项目地址: https://gitcode.com/gh_mirrors/an…

📰

SeaTunnel Email Sink 连接器详解:将数据以附件形式发送邮件

SeaTunnel Email Sink 连接器详解:将数据以附件形式发送邮件 【免费下载链接】seatunnel SeaTunnel is a multimodal, high-performance, distributed, massive data integration tool. 项目地址: https://gitcode.com/GitHub_Trending/se/seatunnel 本文以 S…

📰

yarn.lock 地址不对怎么办?从 signature 哈希到依赖锁定机制全解析

1. 从一个诡异的签名串说起:yarn.lock 里的地址为什么会对不上第一次看到signatureb05c505286f606b32d69ab58ee3e7bf4这串东西挂在photobooth/yarn.lock后面,很多人第一反应是"这是不是某个校验和写错了"。我当初也是这么想的,直到…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬