Golang并发编程-协程goroutine的信道(channel)

文章目录

  • 前言
  • 一、信道的定义与使用
    • 信道的声明
    • 信道的使用
  • 二、信道的容量与长度
  • 三、缓冲信道与无缓冲信道
      • 缓冲信道
      • 无缓冲信道
  • 四、信道的初体验
    • 信道关闭的广播机制
  • 总结


前言

Goroutine的开发,当遇到生产者消费者场景的时候,离不开 channel(信道)的使用。
信道,就是一个管道,连接多个goroutine程序 ,它是一种队列式的数据结构,遵循先入先出的规则。
在这里插入图片描述


一、信道的定义与使用

信道的声明

信道声明的两种方式:

// 先声明再初始化
var 信道实例 chan 信道类型
信道实例 = make(chan 信道类型)// 上面两句合并
信道实例 := make(chan 信道类型)

信道的使用

发送数据,接收数据

// 往信道中发送数据
pipline<- 200// 从信道中取出数据,并赋值给mydata
mydata := <-pipline

信道用完了,可以对其进行关闭,避免有人一直在等待。但是你关闭信道后,接收方仍然可以从信道中取到数据,只是接收到的会永远是 0。

close(pipline)

当从信道中读取数据时,可以有多个返回值,其中第二个可以表示 信道是否被关闭,如果已经被关闭,ok 为 false,若还没被关闭,ok 为true。

x, ok := <-pipline

二、信道的容量与长度

一般创建信道都是使用 make 函数,make 函数接收两个参数

  • 第一个参数:必填,指定信道类型
  • 第二个参数:选填,不填默认为0,指定信道的容量(可缓存多少数据)

对于信道的容量,很重要,这里要多说几点:

  1. 当容量为0时,说明信道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的信道称之为无缓冲信道。
  2. 当容量为1时,说明信道只能缓存一个数据,若信道中已有一个数据,此时再往里发送数据,会造成程序阻塞。 利用这点可以利用信道来做锁。
  3. 当容量大于1时,信道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。

信道的容量,可以使用 cap 函数获取 ,而信道的长度,可以使用 len 长度获取。

package mainimport "fmt"func main() {pipline := make(chan int, 10)fmt.Printf("信道可缓冲 %d 个数据\n", cap(pipline))pipline <- 1pipline <- 1fmt.Printf("信道中当前有 %d 个数据\n", len(pipline))
}

输出结果

[root@work day01]# go run channel.go 
信道可缓冲 10 个数据
信道中当前有 2 个数据

三、缓冲信道与无缓冲信道

按照是否可缓冲数据可分为:缓冲信道无缓冲信道

缓冲信道

允许信道里存储一个或多个数据,这意味着,设置了缓冲区后,发送端和接收端可以处于异步的状态。

pipline := make(chan int, 10)

无缓冲信道

在信道里无法存储数据,这意味着,接收端必须先于发送端准备好,以确保你发送完数据后,有人立马接收数据,否则发送端就会造成阻塞,原因很简单,信道中无法存储数据。也就是说发送端和接收端是同步运行的。

pipline := make(chan int)

四、信道的初体验

信道的用途就是传递数据信息,下面以经典的生产者和消费者场景,实践下信道是使用。
下面的例子中,声明了一个容量为10的传递整型的信道。生产者生产的数据放入信道,消费者将从信道的数据打印。
这里,我们消费者有两种,其实是演示了信道遍历的两种常见的方式。
第一种是用for range方式
第二种是用for循环,断言判断信道是否关闭,关闭了就退出。

package mainimport ("fmt""sync"
)// 生产者
func producer(mychan chan int, wg *sync.WaitGroup) {defer wg.Done()for i := 1; i <= 10; i++ {mychan <- ifmt.Printf("produce: %v\n", i)fmt.Printf("pipeline Length: %v \n", len(mychan))}// 记得 close 信道// 不然主函数中遍历完并不会结束,而是会阻塞。close(mychan)
}//消费者 Range方式
func consumer_range(name string, mychan chan int, wg *sync.WaitGroup) {defer wg.Done()for k := range mychan {fmt.Printf("consumer_range %v: %v\n", name, k)}
}//消费者 断言方式
func consumer_assert(name string, mychan chan int, wg *sync.WaitGroup) {defer wg.Done()for {// if data, ok := <-mychan; ok  ok为bool值,true表示正常接受,false表示通道关闭。if data, ok := <-mychan; ok {fmt.Printf("consumer_assert %v: %v\n", name, data)} else {break}}
}func main() {var wg sync.WaitGrouppipline := make(chan int, 10)wg.Add(3)go producer(pipline, &wg)go consumer_range("ID1", pipline, &wg)go consumer_assert("ID2", pipline, &wg)wg.Wait()}

输出的结果:

[root@work day01]# go run channel3.go 
produce: 1
pipeline Length: 0 
produce: 2
pipeline Length: 1 
produce: 3
pipeline Length: 2 
produce: 4
pipeline Length: 3 
produce: 5
pipeline Length: 4 
produce: 6
pipeline Length: 5 
produce: 7
pipeline Length: 6 
produce: 8
pipeline Length: 7 
produce: 9
pipeline Length: 8 
produce: 10
pipeline Length: 9 
consumer_assert ID2: 1
consumer_assert ID2: 2
consumer_assert ID2: 3
consumer_assert ID2: 4
consumer_assert ID2: 5
consumer_assert ID2: 6
consumer_assert ID2: 7
consumer_assert ID2: 8
consumer_assert ID2: 9
consumer_assert ID2: 10

信道关闭的广播机制

上面的案例中,有一点需要注意,我们的生产者函数中,在数据生产结束后,调用了close(mychan)方法,关闭了信道。如果不关闭程序还能正常执行吗?我们手动修改下代码,将代码改造如下,再次执行。

	// 记得 close 信道// 不然主函数中遍历完并不会结束,而是会阻塞。// close(mychan)

执行结果如下:

[root@work day01]# go run channel3.go 
produce: 1
pipeline Length: 0 
produce: 2
pipeline Length: 0 
produce: 3
pipeline Length: 1 
produce: 4
pipeline Length: 2 
produce: 5
pipeline Length: 3 
produce: 6
pipeline Length: 4 
produce: 7
pipeline Length: 5 
produce: 8
pipeline Length: 6 
produce: 9
pipeline Length: 7 
produce: 10
pipeline Length: 8 
consumer_assert ID2: 1
consumer_assert ID2: 3
consumer_assert ID2: 4
consumer_assert ID2: 5
consumer_assert ID2: 6
consumer_assert ID2: 7
consumer_assert ID2: 8
consumer_assert ID2: 9
consumer_assert ID2: 10
consumer_range ID1: 2
fatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000010060?)/usr/local/go/src/runtime/sema.go:62 +0x27
sync.(*WaitGroup).Wait(0x0?)/usr/local/go/src/sync/waitgroup.go:116 +0x4b
main.main()/data/go_projects/third_packages/goroutine/day01/channel3.go:50 +0x157goroutine 7 [chan receive]:
main.consumer_range({0x49bc4f, 0x3}, 0x0?, 0x0?)/data/go_projects/third_packages/goroutine/day01/channel3.go:25 +0x11f
created by main.main/data/go_projects/third_packages/goroutine/day01/channel3.go:48 +0xf9goroutine 8 [chan receive]:
main.consumer_assert({0x49bc52, 0x3}, 0x0?, 0x0?)/data/go_projects/third_packages/goroutine/day01/channel3.go:34 +0x11d
created by main.main/data/go_projects/third_packages/goroutine/day01/channel3.go:49 +0x14d
exit status 2

通过上面的结果,我们看到消费者在消费完信道中的消息后,就panic退出了。
当程序一直在等待从信道里读取数据,而此时并没有人会往信道中写入数据。此时程序就会陷入死循环,造成死锁。
这里在现实生活中也有类似的例子,生产者是网红,每天摆摊,大量消费者蹲守在网红摊点前打卡,突然一天网红不出摊了,也没在微信和社交媒体更新动态,那消费者一天一直苦等。正确的做法是网红在社交媒体上发布下今天不出摊的通知,避免粉丝等待。

if data, ok := <-mychan; ok  ok为bool值,true表示正常接受,false表示通道关闭。

同理,我们上面的案例一样,当生产者不在生产的时候,应该关闭信道。因为信道关闭是有关播机制的,所有的channel接收者都会在chennel关闭时,立刻从阻塞等待中返回ok值为false。


总结

  1. 关闭一个未初始化的 channel 会产生 panic
  2. 重复关闭同一个 channel 会产生 panic
  3. 向一个已关闭的 channel 发送消息会产生 panic
  4. 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已被读取,则会读取到该类型的零值
  5. 从已关闭的 channel 读取消息永远不会阻塞,并且会返回一个为 false 的值,用以判断该 channel 是否已关闭(x,ok := <- ch)
  6. 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
  7. 如果写端没有写数据,也没有关闭channel 。<-ch; 会阻塞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/331685.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于XtremIO 全闪存储维护的一些坑(建议)

XtremIO 是EMC过去主推的一款全闪存储系统&#xff0c;号称性能小怪兽&#xff0c;对付那些对于性能要求极高的业务场景是比较合适的&#xff0c;先后推出了1代和2代产品&#xff0c;目前这个产品好像未来的演进到了PowerStor或者PowerMax全闪&#xff0c;应该不独立发展这个产…

存在重复元素 II[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个整数数组nums和一个整数k&#xff0c;判断数组中是否存在两个不同的索引i和j&#xff0c;满足nums[i] nums[j]且abs(i - j) < k。如果存在&#xff0c;返回true&#xff1b;否则&#xff0c;返回false。 示例 1&#…

Excel工作簿/表的合并/拆分全集(一文通关)

概述 在工作中&#xff0c;我们常会用到到Excel拆分/合并为多个工作表/簿&#xff0c;如全国的订单表&#xff0c;需要根据省份列拆分下发至对应的省、各省份数据需要汇总、...... 应该如何操作呢&#xff1f; 1. 传统方法&#xff08;借助透视表、Power Query编辑器、VBA实现…

jvm的类加载

文章目录 概要加载类加载器分类双亲委派模型自定义加载器 验证准备解析初始化<cinit>与<init> 概要 jvm运行时的整体结构如下 一个Car类&#xff0c;类跟Car对象的转换过程如下&#xff1a; 加载后的class类信息存放于方法区&#xff1b;ClassLoader只负责clas…

C++ vector类

目录 0.前言 1.vector介绍 2.vector使用 2.1 构造函数(Constructor) 2.1.1. 默认构造函数 (Default Constructor) 2.1.2 填充构造函数 (Fill Constructor) 2.1.3 范围构造函数 (Range Constructor) 2.1.4 拷贝构造函数 (Copy Constructor) 2.2 迭代器(Iterator) 2.2.…

多项式重构的平滑和法线估计-------PCL

多项式重构的平滑和法线估计 /// <summary> /// 多项式重构的平滑和法线估计 /// </summary> /// <param name"cloud"></param> /// <returns>输出一个包含平滑后的点云数据以及相应法线信息的数据结构</returns> pcl::PointCl…

《计算机网络微课堂》课程概述

​ 课程介绍 本专栏主要是 B 站课程《计算机网络微课堂》的文字版&#xff0c;作者是湖南科技大学的老师。 B 站地址&#xff1a;https://www.bilibili.com/video/BV1c4411d7jb 该课程好评如潮&#xff0c;包含理论课&#xff0c;实验课&#xff0c;考研真题分析课&#xf…

阅读笔记——《ProFuzzBench: A Benchmark for Stateful Protocol Fuzzing》

【参考文献】Natella R, Pham V T. Profuzzbench: A benchmark for stateful protocol fuzzing[C]//Proceedings of the 30th ACM SIGSOFT international symposium on software testing and analysis. 2021: 662-665.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&…

Day01-Web开发、介绍、HTML

一、什么是 Web ? Web:全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 <!-- 文档类型为HTML --> <!DOCTYPE html> <html lang"en"> <head><!-- 字符集 --><meta charset"U…

移动端开发 笔记01

目录 01 移动端的概述 02 移动端的视口标签 03 开发中的二倍图 04 流式布局 05 弹性盒子布局 01 移动端的概述 移动端包括:手机 平板 便携式设备 目前主流的移动端开发: 安卓设备 IOS设备 只要移动端支持浏览器 那么就可以使用浏览器开发移动端项目 开发移动端 使用…

AI视频教程下载:全面掌握ChatGPT和LangChain开发AI应用(附源代码)

这是一门深入的课程&#xff0c;涉及ChatGPT、LangChain和Python。打造专注于现实世界AI集成的AI应用&#xff0c;课件附有每一节涉及到的源代码。 **你将学到什么&#xff1a;** - 将ChatGPT集成到LangChain的生产风格应用中 - 使用LangChain组件构建复杂的文本生成管道 - …

开放式耳机哪个品牌音质好用又实惠耐用?五大公认卷王神器直入!

​在现今耳机市场&#xff0c;开放式耳机凭借其舒适的佩戴体验和独特的不入耳设计&#xff0c;备受消费者追捧。它们不仅让你在享受音乐时&#xff0c;仍能察觉周围的声音&#xff0c;确保与人交流无障碍&#xff0c;而且有利于耳朵的卫生与健康。对于运动爱好者和耳机发烧友而…

源码编译安装LAMP

LAMP架构 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务及其应用开发环境。LAMP是一个缩写词&#xff0c;具体包括Linux操作系统、Apache网站服务器、MySQL数据库服务器、PHP&#xff08;或…

sqlserver的查询(三)

目录 10. group by(分组) 11. having(对分组后的信息过滤) 可能从这里开始&#xff0c;执行顺序越来越显得重要了&#xff01;&#xff01;&#xff01; 10. group by(分组) 这个查询相比前面会有一些困难&#xff1b; 格式&#xff1a;group by 字段的集合&#xff1b; 功…

Maven多环境打包配置

一、启动时指定环境配置文件 在启动springboot应用的jar包时&#xff0c;我们可以指定配置文件&#xff0c;通常把配置文件上传到linux服务器对应jar包的同级目录&#xff0c;或者统一的配置文件存放目录 java -jar your-app.jar --spring.config.location/opt/softs/applicat…

NodeJS安装并生成Vue脚手架(保姆级)

文章目录 NodeJS下载配置环境变量Vue脚手架生成Vue脚手架创建项目Vue项目绑定git 更多相关内容可查看 NodeJS下载 下载地址&#xff1a;https://nodejs.org/en 下载的速度应该很快&#xff0c;下载完可以无脑安装&#xff0c;以下记得勾选即可 注意要记住自己的安装路径&…

Linux--线程的认识(一)

线程的概念 线程&#xff08;Thread&#xff09;是操作系统中进行程序执行的最小单位&#xff0c;也是程序调度和分派的基本单位。它通常被包含在进程之中&#xff0c;是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程中可以并发多个线…

Redis内存回收-内存淘汰策略

LFU的访问次数之所以叫做逻辑访问次数&#xff0c;是因为并不是每次key被访问都计数&#xff0c;而是通过运算&#xff1a; 生成0~1之间的随机数R计算 (旧次数 * lfu_log_factor 1)&#xff0c;记录为P如果 R < P &#xff0c;则计数器 1&#xff0c;且最大不超过255访问…

二叉树详解

目录 一、二叉树的实现 1.1 二叉树的前序遍历 1.2 二叉树的中序遍历 1.3 二叉树的后续遍历 1.4 二叉树的节点个数 1.5 二叉树叶子节点个数 1.6 二叉树查找值为x的节点 1.7 二叉树第k层节点个数 1.8 二叉树的高度 1.9 二叉树的销毁 二、代码展示 BTNode.h BTNode.c 最后 一…

skynet.newservice简介:服务的启动

skynet是一个轻量级的游戏服务器框架。 简介 在skynet的体系中&#xff0c;服务是一个基础概念。通常&#xff0c;我们使用skynet.newservice来启动一个snlua服务。 那么&#xff0c;当我们写下local addr skynet.newservice("test")这行代码时&#xff0c;系统是怎…