资讯动态

Go语言通道(channel):Goroutine通信、同步与select多路并发控制

发布时间:2026/9/5 9:37:41 来源:尧图企业网站定制
前言Go语言最核心的并发理念不要通过共享内存来通信而要通过通信来实现共享内存。Goroutine是轻量级协程多个Goroutine之间内存默认互相隔离如果直接共享变量就要加锁处理竞争容易写出bug。而channel(通道)就是Goroutine之间专门用来传递数据、做同步协调的管道配合select关键字可以实现多路IO监听完成复杂并发控制。一、Channel基础概念channel 是Go内置类型类似一个队列遵循先进先出。专门用于Goroutine之间传递数据实现协程之间的数据交互与同步。1. 通道声明与创建// 声明通道nil通道不能直接使用varchchanint// make创建通道chmake(chanint)// 无缓冲通道chBuf:make(chanint,3)// 带缓冲通道缓冲区大小3语法make(chan 数据类型, [缓冲大小])无缓冲通道发送和接收必须同时就绪会发生阻塞用于协程同步。带缓冲通道缓冲区未满时发送不会阻塞缓冲区有数据接收不会阻塞。2. 通道基本操作通道一共只有3种操作发送、接收、关闭ch-10// 向通道发送数据10x:-ch// 从通道接收数据赋值给xclose(ch)// 关闭通道⚠️注意不要重复关闭同一个通道会panic。不要向已经关闭的通道发送数据会panic。从关闭的通道读取数据不会阻塞返回对应类型零值。二、Goroutine之间使用Channel通信Goroutine之间不共享变量把数据丢进channel另一个协程读取完成数据传递。packagemainimportfmtfuncworker(chchanstring){// 子协程往通道发送数据ch-子goroutine完成任务}funcmain(){ch:make(chanstring)goworker(ch)// main协程接收子协程的数据msg:-ch fmt.Println(msg)fmt.Println(主程序结束)}输出子goroutine完成任务 主程序结束这里无缓冲通道天然实现同步main会阻塞等待子协程写入数据不需要time.Sleep来等待goroutine执行完毕。三、Channel实现同步示例1利用通道等待goroutine执行完毕替代sync.WaitGroup简易版本packagemainimportfmtfunctask(donechanbool){fmt.Println(子协程正在执行任务)done-true// 任务完成发送信号}funcmain(){done:make(chanbool)gotask(done)-done// main阻塞等待子协程信号fmt.Println(收到信号子协程执行完成main退出)}通道不仅仅传业务数据还可以传递信号做协程同步。无缓冲 vs 带缓冲通道区别//无缓冲发送之后立刻阻塞必须有人接收ch1:make(chanint)//缓冲2可以先写入2个数据不会阻塞ch2:make(chanint,2)ch2-1ch2-2四、select多路通道监听多路并发控制select是Go专门为channel设计的语法作用同时监听多个channel的读写操作哪个case通道就绪就执行哪个case。如果多个case同时就绪随机选一个执行如果都没就绪阻塞直到有case就绪搭配default不会阻塞没有就绪case直接执行default1. select基础示例packagemainimport(fmttime)funcmain(){ch1:make(chanstring)ch2:make(chanstring)gofunc(){time.Sleep(1*time.Second)ch1-来自通道1的数据}()gofunc(){time.Sleep(2*time.Second)ch2-来自通道2的数据}()// select多路监听fori:0;i2;i{select{casev1:-ch1:fmt.Println(v1)casev2:-ch2:fmt.Println(v2)}}fmt.Println(全部接收完成)}2. select default 非阻塞通道操作没有数据可读不会卡住直接走default分支packagemainimportfmtfuncmain(){ch:make(chanint)select{caseval:-ch:fmt.Println(读到数据,val)default:fmt.Println(通道暂时没有数据非阻塞跳过)}}3. select实现超时控制开发高频场景packagemainimport(fmttime)funcmain(){ch:make(chanstring)gofunc(){time.Sleep(3*time.Second)//模拟耗时操作ch-任务结果}()select{caseres:-ch:fmt.Println(res)case-time.After(2*time.Second):// 2秒超时fmt.Println(执行超时)}}time.After()返回一个channel时间到自动发送数据非常适合做并发超时。五、通道range遍历当通道持续发送数据可以用for range读取通道通道关闭会自动退出循环packagemainimportfmtfuncmain(){ch:make(chanint,3)ch-1ch-2ch-3close(ch)// range读取通道通道关闭循环自动结束forv:rangech{fmt.Println(v)}fmt.Println(读取结束)}六、单向通道约束读写权限可以定义只读通道、只写通道在函数参数做约束代码健壮性更好。packagemainimportfmt// ch 只写通道只能发送数据funcsend(chchan-int){ch-100}// ch 只读通道只能读取数据funcrecv(ch-chanint){fmt.Println(-ch)}funcmain(){ch:make(chanint)send(ch)recv(ch)}七、常见坑点总结nil通道读写nil通道会永久阻塞。var ch chan int没有make直接使用会死锁。忘记closerange读取通道如果通道不关闭range会永久阻塞。重复关闭/关闭后发送直接panic。无缓冲通道忘记goroutinemain直接往无缓冲通道写数据没有协程接收直接死锁。八、总结channel是goroutine之间通信的载体遵循通过通信共享内存不要共享内存通信。无缓冲通道侧重同步带缓冲通道侧重数据队列。channel三大操作发送、接收、关闭。select实现多路通道监听可以完成多路并发、非阻塞读写、超时控制。for‑range遍历通道通道关闭自动退出循环。单向通道约束函数参数读写权限提升代码安全性。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价