当前位置: 首页 > news >正文

Go 协程通道使用注意

目录

关闭channel

引入

 不关闭通道是有风险的,主要存在两条:

如何优雅的关闭?

1.只有一个发送者

2.多个发送者

判断通道是否关闭

有缓存和无缓存的通道有什么区别?

错误的关闭通道


关闭channel

close(chan):关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入。

引入

看一看下面的例子:

ch := make(chan int, 1)
ch <- 11
// close(ch) // 如果不关闭,下面会引发死锁// 通道没有关闭的时候会阻塞,程序会永远等待从通道中接收值。而且由于没有其他goroutine来关闭通道,这个阻塞状态将会持续下去,从而引发死锁。
for v := range ch {fmt.Println(v)
}

 不关闭通道是有风险的,主要存在两条:

  1. 出现死锁:如果通道不会再被使用但未关闭,接收者可能会一直等待数据,导致死锁或无意义的等待。
  2. 出现资源泄漏:未关闭的通道可能会导致资源泄漏,因为垃圾收集器不会回收未关闭的通道。而且还要消耗电脑的性能来维护这条管道。

如何优雅的关闭?

在关闭管道的时候,我们要注意:正确地决定何时关闭通道、确保通道不会被多次关闭。(多次关闭同一个通道会报panic)

1.只有一个发送者

如果只有一个发送者,那么这个发送者在完成所有发送操作后可以直接关闭通道:

package mainimport ("fmt""time"
)// 边入边出
func main() {c := make(chan int, 5)go fibonacci(10, c)for v := range c {fmt.Println("out:", time.Now())fmt.Println(v)}
}// 应该由发送数据的一方关闭通道,当数据发送完毕后就是关闭通道的时候。
func fibonacci(n int, c chan int) {x, y := 0, 1for i := 0; i < n; i++ {c <- xfmt.Println("in:", time.Now())time.Sleep(100)x, y = y, x+y}close(c)
}// 大致运行结果:
// in: 2024-07-16 16:00:34.637655 +0800 CST m=+0.000171297
// out: 2024-07-16 16:00:34.637681 +0800 CST m=+0.000197020
// in: 2024-07-16 16:00:34.637952 +0800 CST m=+0.000467725
// 0
// in: 2024-07-16 16:00:34.637963 +0800 CST m=+0.000478508
// out: 2024-07-16 16:00:34.637968 +0800 CST m=+0.000483915
// 1
// in: 2024-07-16 16:00:34.637976 +0800 CST m=+0.000491801
// in: 2024-07-16 16:00:34.637983 +0800 CST m=+0.000499051
// out: 2024-07-16 16:00:34.637978 +0800 CST m=+0.000494157
// 1
// out: 2024-07-16 16:00:34.637999 +0800 CST m=+0.000514485
// 2
// out: 2024-07-16 16:00:34.638006 +0800 CST m=+0.000522241
// 3
// out: 2024-07-16 16:00:34.638013 +0800 CST m=+0.000528591
// 5
// in: 2024-07-16 16:00:34.637988 +0800 CST m=+0.000504017
// in: 2024-07-16 16:00:34.638039 +0800 CST m=+0.000555021
// in: 2024-07-16 16:00:34.638045 +0800 CST m=+0.000561340
// in: 2024-07-16 16:00:34.638049 +0800 CST m=+0.000564725
// in: 2024-07-16 16:00:34.638055 +0800 CST m=+0.000570642
// out: 2024-07-16 16:00:34.638052 +0800 CST m=+0.000568007
// 8
// out: 2024-07-16 16:00:34.638095 +0800 CST m=+0.000610606
// 13
// out: 2024-07-16 16:00:34.638103 +0800 CST m=+0.000619251
// 21
// out: 2024-07-16 16:00:34.63811 +0800 CST m=+0.000626188
// 34

2.多个发送者

当有多个发送者时,可以使用 sync.WaitGroup 来协调这些发送者,并在所有发送者完成后由一个协程关闭通道:

package mainimport ("fmt""sync"
)func main() {ch := make(chan string)var wg sync.WaitGroup// 启动3个协程发送数据for i := 1; i <= 5; i++ {wg.Add(1)go func(num int) {defer wg.Done()for j := 'a'; j <= 'e'; j++ {ch <- fmt.Sprintf("协程%d:%c", num, j)}}(i)}// 启动一个协程来等待所有发送者完成并关闭通道go func() {wg.Wait()close(ch)}()// 接收数据for val := range ch {fmt.Println(val)}fmt.Println("通道已关闭,结束接收")
}

判断通道是否关闭

v, ok := <-ch

如果 ok 为 true,表示成功从 channel 中接收到一个值,并且 channel 还没有关闭。如果 ok 为 false,表示 channel 已经关闭。

当一个 channel 已经关闭而且其中的元素已经全部被取出时,再从管道中取出数据会返回该元素类型的零值,并且 ok 会被设置为 false。这样的检查是为了防止在已关闭的 channel 上进行接收操作时,引发 panic。因为在已关闭的 channel 上进行接收操作会立即返回零值,但如果不进行检查,可能会误认为是从 channel 中接收到了有效的数据。因此,通过检查 ok,我们可以确定是否成功接收到了有效的值。

    c := make(chan int, 5)c <- 1c <- 2c <- 3close(c)v1, ok := <-cfmt.Println(v1, ok) // 1 truev1, ok = <-cfmt.Println(v1, ok) // 2 truev1, ok = <-cfmt.Println(v1, ok) // 3 truev1, ok = <-cfmt.Println(v1, ok) // 0 falsev1, ok = <-cfmt.Println(v1, ok) // 0 false

有缓存和无缓存的通道有什么区别?

参考下面链接:

golang channel有无缓冲区的区别

错误的关闭通道

错误一:多次关闭同一个通道

    c := make(chan int, 5)for i := 0; i < 5; i++ {defer close(c) // 报panic:同一个通道关闭了多次}

错误二:资源泄漏

    for i := 0; i < 5; i++ {c := make(chan int, 5)defer close(c)}

看起来没有错误,运行出来也没有错误。但是可能会导致资源泄漏,因为每次循环迭代中创建的通道不会立即关闭,导致内存和其他资源的泄漏。这五个管道的关闭时间都是在:所在函数运行完毕的时候关闭的。而不是本次循环结束后就立刻关闭。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 在设计电气系统时,电气工程师需要考虑哪些关键因素?
  • 云计算数据中心(二)
  • 【C语言】条件运算符详解 - 《 A ? B : C 》
  • java代理模式之JDK动态代理
  • Bigdata-Docker构建大数据学习开发环境
  • git回退分支版本git reset --hard HEAD
  • Beelzebub过程记录及工具集
  • PG大会周五于杭州举办;Pika发布4.0;阿里云MySQL上线Zero-ETL集成能力
  • vue3前端页面下载excel模版
  • c#中的字符串方法
  • Linux C++ 056-设计模式之迭代器模式
  • 【D3.js in Action 3 精译】1.3 D3 视角下的数据可视化最佳实践(下)
  • opencv—常用函数学习_“干货“_9
  • 常见的排序算法,复杂度
  • jail子系统里升级Ubuntu focal到jammy
  • Java 内存分配及垃圾回收机制初探
  • JAVA 学习IO流
  • javascript从右向左截取指定位数字符的3种方法
  • JS题目及答案整理
  • Linux后台研发超实用命令总结
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • python大佬养成计划----difflib模块
  • supervisor 永不挂掉的进程 安装以及使用
  • 包装类对象
  • 构建工具 - 收藏集 - 掘金
  • 力扣(LeetCode)965
  • 前端之React实战:创建跨平台的项目架构
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 7行Python代码的人脸识别
  • C# - 为值类型重定义相等性
  • ## 基础知识
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #NOIP 2014# day.2 T2 寻找道路
  • #Z2294. 打印树的直径
  • (CVPRW,2024)可学习的提示:遥感领域小样本语义分割
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第1节 (全局数据、栈和堆)
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (windows2012共享文件夹和防火墙设置
  • (ZT)薛涌:谈贫说富
  • (第30天)二叉树阶段总结
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (三分钟)速览传统边缘检测算子
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • ***利用Ms05002溢出找“肉鸡
  • .gitattributes 文件
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .NET gRPC 和RESTful简单对比
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .NET 某和OA办公系统全局绕过漏洞分析