3.10 Go之缓冲通道
缓冲通道种类
无缓冲通道
带缓冲通道
什么是无缓冲(Unbuffered Channel)通道?
指在接收前没有能力保存任何值的通道--->一进一出(只能进一个值)
特点:
要求发送goroutine
和接收goroutine
同时准备好,才能完成发送和接收操作
理解:
可以理解为把两个goroutine
和一个channel
当作一个原子进行事务操作。有一方失败则全部失败
专业概念:
如果两个goroutine
没有同时准备好通道将会阻塞,通道进行发送和接收的交互行为本身就是同步的.任意一个操作都无法离开另一个操作单独存在
无缓冲通道之打球
模拟的是再无缓冲状态下两个goroutine
和一个channel
一起协作的过程:
package main
import (
"fmt"
"math/rand"
"sync"
)
/*
通过等待goroutine来判断此时有多少个goroutine协程再准备
声明一个函数,该函数进行通道相关的操作
*/
/* 声明一个goroutine组 */
var wgNo5 sync.WaitGroup
/*
玩家函数,每个玩家都有相同的操作
*/
func player(name string, court chan int) {
// 函数退出处理
defer wgNo5.Done()
// 开始进行通道内操作
for {
// 等待数据从通道中被取出--->判断是否击球
/*
goroutine从通道接收数据,用来表示等待接球。
这个接收动作会锁住goroutine,直到有数据发送到通道里。通道的接收动作返回时。--->直到最后一行代码执行
*/
ball, ok := <-court
/*
检测ok标志是否为false。如果这个值是false,表示通道已经被关闭,游戏结束
*/
if !ok {
// 说明通道关闭,返回了false
fmt.Printf("%s Won\n", name)
// 返回结果
return
}
// 通过随机数判断是否未从通道中取出数据
n := rand.Intn(100)
// 判断--->随机数摸13为0说明丢球
if n%13 == 0 {
fmt.Printf("%s Missed\n", name)
// 通道关闭--->代表输了
/*
某个goroutine没有打中球,关闭通道。之后两个goroutine都会返回,通过defer声明的Done会被执行,程序终止
*/
close(court)
return
}
// 显示击球数,将击球数+1
fmt.Printf("%s Hit %d\n", name, ball)
ball++
// 将数据放回通道--->回击
/*
将ball作为球重新放入通道,发送给另一位选手。在这个时刻,两个goroutine都会被锁住,直到交换完成
*/
court <- ball
}
}
/*
实际调用
*/
func main() {
// 创建一个通道
ch := make(chan int)
// 声明计数器--->要等待几个goroutine
wgNo5.Add(2)
// 开启两个goroutine
go player("Lucifer", ch)
go player("JunkingBoy", ch)
// 通道当中先写入一条数据作为起始
ch <- 1
// 等待goroutine结束
wgNo5.Wait()
}
特点:
再通道当中的数据都是被锁住的。只有两个goroutine
同时准备好执行接收和发送的操作了才会解锁
无缓冲通道之接力赛跑
接力赛跑保证的是交接棒的同步,可以抽象为通道当中的两个goroutine
同步发生操作
package main
import (
"fmt"
"sync"
"time"
)
/*
声明一个计时器
声明一个runner函数,再runner当中定义runner的行为,包括两个runner的协作
*/
/* 声明一个计数器 */
var wgNo6 sync.WaitGroup
func Runner(baton chan int) {
// 声明一个runner变量
var newRunner int
// 将通道中的第一个runner(可能不是接力当中的第一个)赋值
runner := <-baton
// 环绕的跑道跑圈
fmt.Printf("第 %d 跑步者正在跑步\n", runner)
// 不是最后一位则创建新的跑步者
if runner != 4 {
newRunner = runner + 1
fmt.Printf("第 %d 跑步者是下一位接力人!\n", newRunner)
// 此时开启一个协程.代表下一位跑步者--->递归的用法相当于每次只要不为4就为当前goroutine构建一个接收的goroutine
/*
一旦接力棒传了进来,就会创建一位新跑步者,准备接力下一棒,直到 goroutine 是第四个跑步者。
*/
go Runner(baton)
}
// 一圈四百米三十五秒
time.Sleep(380 * time.Millisecond)
// 判断是否是最后一位接力人
if runner >= 4 {
fmt.Printf("接力人 %d 已到终点!", runner)
// 调用计时器的don函数--->接力完成
wgNo6.Done()
// 结束
return
}else {
// 如果不为4则把接力棒交给下一位
fmt.Printf("接力者 %d 交接棒给下一位接力者 %d !\n", runner, newRunner)
// 将交接棒放回通道
baton <- newRunner
}
}
/* main函数进行调用 */
func main() {
// 创建一个无缓冲通道
ch := make(chan int)
// 设置计时器--->为最后一位跑步者添加一位计时器--->因为无缓冲需要两个goroutine
wgNo6.Add(1)
// 创建第一位跑步者
go Runner(ch)
// 开始进行接力
ch <- 1
// 协程等待
wgNo6.Wait()
}
什么是带缓冲(Buffered Channel)通道?
指在被接收前能存储一个或者多个值的通道
特点:
-
不强制要求
goroutine
之间必须同时完成发送和接收 -
通道中没有可用缓冲区容纳发送的值时发送动作才会阻塞
-
通道中没有要接收的值时接收动作才会阻塞
带缓冲通道实现原理
在无缓冲通道的基础上,为通道增加一个有限大小的存储空间
无缓冲和带缓冲的区别
无缓冲通道保证数据的收发是同步的
带缓冲通道使得数据收发变成了异步的
创建带缓冲的通道
通道实例 := make(chan 通道类型, 缓冲大小)
-
通道类型:通道发送和接收的数据类型.
-
缓冲大小:通道最多可以保存的元素数量.
-
通道实例:被创建出的通道实例.
示例代码:
package main
import "fmt"
func main() {
// 创建一个可以接收三个元素缓冲大小的通道
ch := make(chan int, 3)
// 查看当前通道的大小
fmt.Println(len(ch))
// 依次向通道当中放值
/*
注意len获取的内容--->len获取的是当前对象的长度而不会获取到缓冲区的大小(底层上看可能连存储区域都未曾开辟.不像数组那样,数组声明的区域以后len可以获取到长度)
*/
//var arr [3]int
//fmt.Println(len(arr))
for i := 0; i < 3; i++ {
ch <- i
}
// 再次查看通道的大小
fmt.Println(len(ch))
}
注意:
-
len()
函数刚开始并不会获取到通道缓冲区的大小,所以for
循环的控制条件填len()
并不能往通道中放值 -
len()
获取的是当前通道中的长度--->也就是说有值了以后才会有长度,没值没有长度 -
缓冲区不像数组一样,声明的大小以后
len()
函数可以直接获取到
带缓冲通道阻塞条件
-
缓冲通道被填满时,再次发送数据给通道会阻塞
-
缓冲通道为空时,尝试从通道当中接收数据
限制通道长度的原因:
-
防止内存崩溃
-
约束提供方的数据提供速度--->数据量必须在消费方处理量+通道长度的范围内,才能正常地处理数据。
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!