Go语言Channel
在本教程中,我们将讨论Channel以及 Goroutines 如何使用Channel进行通信。
什么是Channel
Channel可以被认为是 Goroutine 用来进行通信的管道。与水在管道中从一端流向另一端的方式类似,可以使用Channel从一端发送数据并从另一端接收数据。
声明Channel
每个Channel都有一个与其关联的类型。该类型是Channel允许传输的数据类型。不允许使用该Channel传输其他类型。
chan T是一个Channel类型是T
Channel的零值为nil。nil的Channel没有任何用处,因此必须使用make类似于map和slices来定义Channel。
让我们编写一些声明Channel的代码。
package mainimport "fmt"func main() { var a chan intif a == nil {fmt.Println("channel a is nil, going to define it")a = make(chan int)fmt.Printf("Type of a is %T", a)}
}
Run program in playground
第 1 行中声明的Channel,此时的Channel是nil。因此,会执行 if 条件内的语句并定义Channel。该程序将输出,
channel a is nil, going to define it
Type of a is chan int
与往常一样,简写声明也是定义Channel的有效且简洁的方式。
a := make(chan int)
上面的代码行还定义了一个 int Channel变量a。
从Channel发送和接收
下面给出了从Channel发送和接收数据的语法:
data := <- a // read from channel a
a <- data // write to channel a
箭头相对于Channel的方向指定是发送还是接收数据。
在第一行中,箭头指向外部a,因此我们从Channel读取a并将值存储到变量data。
在第二行中,箭头指向a,因此我们正在向写入通道写入a。
默认情况下发送和接收是阻塞的
默认情况下,向Channel发送和接收是阻塞的。这是什么意思?当数据发送到Channel时,控制会在发送语句中被阻塞,直到其他 Goroutine 从该Channel读取数据。类似地,当从Channel读取数据时,读取会被阻塞,直到某个 Goroutine 将数据写入该Channel。
Channel的这一属性有助于 Goroutines 有效地进行通信,而无需使用其他编程语言中很常见的显式锁或条件变量。
如果现在这没有意义也没关系。接下来的部分将更清楚地说明默认情况下Channel是如何阻塞的。
Channel示例程序
让我们编写一个程序来了解 Goroutine 如何使用Channel进行通信。
让我引用上一篇教程中的程序。
package mainimport ( "fmt""time"
)func hello() { fmt.Println("Hello world goroutine")
}
func main() { go hello()time.Sleep(1 * time.Second)fmt.Println("main function")
}
Run program in playground
这是上一个教程中的程序。我们在这里使用 sleep 来让主 Goroutine 等待 hello Goroutine 完成。
我们将使用Channel重写上面的程序。
package mainimport ( "fmt"
)func hello(done chan bool) { fmt.Println("Hello world goroutine")done <- true
}
func main() { done := make(chan bool)go hello(done)<-donefmt.Println("main function")
}
Run program in playground
在上面的程序中,我们在第 1 行创建了一个布尔Channel。 并将其作为参数传递给helloGoroutine。在14行号中。 我们正在从doneChannel接收数据。这行代码是阻塞的,这意味着在某个 Goroutine 将数据写入Channel之前done,控件不会移动到下一行代码。因此,这消除了原始程序中存在的用于防止主 Goroutine 退出的需要time.Sleep。
该代码行<-done从完成的Channel接收数据,但不使用该数据或将该数据存储在任何变量中。这是完全合法的。
现在我们的mainGoroutine 被阻塞,等待完成Channel上的数据。Goroutinehello接收此Channel作为参数,打印Hello world goroutine然后写入done。当此写入完成时,主 Goroutine 会从 did Channel接收数据,解除阻塞,然后打印主函数。
该程序输出
Hello world goroutine
main function
让我们通过在 Goroutine 中引入 sleep 来修改此程序,hello以更好地理解这种阻塞概念。
package mainimport ( "fmt""time"
)func hello(done chan bool) { fmt.Println("hello go routine is going to sleep")time.Sleep(4 * time.Second)fmt.Println("hello go routine awake and going to write to done")done <- true
}
func main() { done := make(chan bool)fmt.Println("Main going to call hello go goroutine")go hello(done)<-donefmt.Println("Main received data")
}
Run program in playground
在上面的程序中,我们在第 10 行的函数中引入了 4 秒的睡眠。.
该程序将首先打印Main going to call hello go goroutine. 然后 hello Goroutine 将启动并打印hello go routine is going to sleep。打印完此信息后,helloGoroutine 将休眠 4 秒,在此期间,mainGoroutine 将被阻塞,因为它正在等待第 18 行完成Channel的数据<-done。hello go routine awake and going to write to done后将打印,然后是Main received data.
Main going to call hello go goroutine
hello go routine is going to sleep
hello go routine awake and going to write to done
Main received data
Channel的另一个例子
让我们再编写一个程序来更好地理解Channel。该程序将打印一个数字的各个数字的平方和和立方之和。
例如,如果输入为 123,则该程序将计算输出为
正方形 = (1 * 1) + (2 * 2) + (3 * 3)
立方体 = (1 * 1 * 1) + (2 * 2 * 2) + (3 * 3 * 3)
输出 = 正方形 + 立方体 = 50
我们将构建该程序,使得平方在单独的 Goroutine 中计算,立方体在另一个 Goroutine 中计算,最终求和发生在主 Goroutine 中。
package mainimport ( "fmt"
)func calcSquares(number int, squareop chan int) { sum := 0for number != 0 {digit := number % 10sum += digit * digitnumber /= 10}squareop <- sum
}func calcCubes(number int, cubeop chan int) { sum := 0 for number != 0 {digit := number % 10sum += digit * digit * digitnumber /= 10}cubeop <- sum
} func main() { number := 589sqrch := make(chan int)cubech := make(chan int)go calcSquares(number, sqrch)go calcCubes(number, cubech)squares, cubes := <-sqrch, <-cubechfmt.Println("Final output", squares + cubes)
}
Run program in playground
第 7行中的函数calcSquares。 计算该数字各个数字的平方和并将其发送到squareop。同样,第 17 行中的calcCubes函数。计算数字各个数字的立方和并将其发送到cubeop。
这两个函数在第31行 和 32行中作为单独的 Goroutine 运行。每个Channel都传递一个要写入的参数作为参数。主 Goroutine 在第 33 行等待来自这两个Channel的数据。 一旦从两个Channel接收到数据,它们就会被存储在squares和cubes变量中,并计算和打印最终输出。该程序将打印
Final output 1536
死锁
使用Channel时要考虑的一个重要因素是死锁。如果一个 Goroutine 正在Channel上发送数据,那么预计其他一些 Goroutine 应该正在接收数据。如果这没有发生,那么程序将在运行时出现Deadlock。
类似地,如果一个 Goroutine 正在等待从某个Channel接收数据,那么其他一些 Goroutine 就应该在该Channel上写入数据,否则程序将会出现Deadlock。
package mainfunc main() { ch := make(chan int)ch <- 5
}
Run program in playground
在上面的程序中,ch创建了一个Channel,我们在第6 行发送5到该Channel。在此程序中,没有其他 Goroutine 正在从Channel接收数据。因此,该程序将因以下运行时错误而发生恐慌。
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main() /tmp/sandbox046150166/prog.go:6 +0x50
单向Channel
到目前为止我们讨论的所有Channel都是双向Channel,即数据可以在它们上发送和接收。还可以创建单向Channel,即仅发送或接收数据的Channel。
package mainimport "fmt"func sendData(sendch chan<- int) { sendch <- 10
}func main() { sendch := make(chan<- int)go sendData(sendch)fmt.Println(<-sendch)
}
Run program in playground
在上面的程序中,我们sendch在第 10 行创建仅发送Channel。chan<- int箭头指向 时表示仅发送Channel。我们尝试从在的仅发送Channel接收数据。这是不允许的,当程序运行时,编译器会,
./prog.go:12:14:无效操作:<-sendch(从仅发送类型 chan<- int 接收)
一切都很好,但如果无法读取,写入仅发送Channel有什么意义!
这就是Channel转换发挥作用的地方。可以将双向Channel转换为仅发送或仅接收Channel,但反之则不然。
package mainimport "fmt"func sendData(sendch chan<- int) { sendch <- 10
}func main() { chnl := make(chan int)go sendData(chnl)fmt.Println(<-chnl)
}
Run program in playground
上面程序的在10行号中。chnl创建了一个双向Channel。它作为参数传递给sendData Goroutine。. 该sendData函数在第 6行将此Channel转换为仅发送Channel。所以现在Channel仅在sendDataGoroutine 内部发送,但在主 Goroutine 中是双向的。该程序将打印10为输出。
关闭Channel和Channel上的范围循环
发送方能够关闭Channel,以通知接收方不再有数据在Channel上发送。
接收者在从Channel接收数据时可以使用附加变量来检查Channel是否已关闭。
v, ok := <- ch
ok如果通过成功发送到Channel的操作接收到该值,则上述语句为 true。如果ok为 false,则意味着我们正在从封闭的Channel中读取数据。从关闭的Channel读取的值将是该Channel类型的零值。例如,如果Channel是一个intChannel,那么从关闭的Channel接收到的值将为0。
package mainimport ( "fmt"
)func producer(chnl chan int) { for i := 0; i < 10; i++ {chnl <- i}close(chnl)
}
func main() { ch := make(chan int)go producer(ch)for {v, ok := <-chif ok == false {break}fmt.Println("Received ", v, ok)}
}
Run program in playground
在上面的程序中,producerGoroutine 将 0 到 9 写入chnlChannel,然后关闭Channel。main 函数for在第 16 行有一个无限循环,它使用第 16 行中的变量检查Channel是否关闭ok。如果ok为 false,则表示Channel已关闭,因此循环已中断。ok否则打印接收到的值和 的值。该程序打印,
Received 0 true
Received 1 true
Received 2 true
Received 3 true
Received 4 true
Received 5 true
Received 6 true
Received 7 true
Received 8 true
Received 9 true
for循环的for range形式可用于从Channel接收值,直到Channel关闭为止。
让我们使用 for range 循环重写上面的程序。
package mainimport ( "fmt"
)func producer(chnl chan int) { for i := 0; i < 10; i++ {chnl <- i}close(chnl)
}
func main() { ch := make(chan int)go producer(ch)for v := range ch {fmt.Println("Received ",v)}
}
Run program in playground
第 16 行中的循环for range。 从Channel接收数据ch直至关闭。一旦ch关闭,循环就会自动退出。该程序输出,
Received 0
Received 1
Received 2
Received 3
Received 4
Received 5
Received 6
Received 7
Received 8
Received 9
可以使用 for range 循环重写Channel部分的另一个示例中的程序,以提高代码可重用性。
如果仔细观察该程序,您会发现查找数字的各个数字的代码在calcSquares函数和calcCubes函数中都重复出现。我们将该代码移至其自己的函数中并同时调用它。
package mainimport ( "fmt"
)func digits(number int, dchnl chan int) { for number != 0 {digit := number % 10dchnl <- digitnumber /= 10}close(dchnl)
}
func calcSquares(number int, squareop chan int) { sum := 0dch := make(chan int)go digits(number, dch)for digit := range dch {sum += digit * digit}squareop <- sum
}func calcCubes(number int, cubeop chan int) { sum := 0dch := make(chan int)go digits(number, dch)for digit := range dch {sum += digit * digit * digit}cubeop <- sum
}func main() { number := 589sqrch := make(chan int)cubech := make(chan int)go calcSquares(number, sqrch)go calcCubes(number, cubech)squares, cubes := <-sqrch, <-cubechfmt.Println("Final output", squares+cubes)
}
Run program in playground
上面程序中的函数digits现在包含从数字中获取各个数字的逻辑,并且由 和calcSquares函数calcCubes同时调用。一旦号码中不再有数字,则第13 行Channel将关闭。.calcSquares和calcCubesGoroutine 使用for range循环监听各自的Channel,直到关闭。程序的其余部分是相同的。该程序还将打印
Final output 1536
本教程到此结束。Channel中还有一些概念,例如缓冲Channel、工作池和select。我们将在他们自己的单独教程中讨论它们。谢谢阅读。祝你有美好的一天。
相关文章:
Go语言Channel
在本教程中,我们将讨论Channel以及 Goroutines 如何使用Channel进行通信。 什么是Channel Channel可以被认为是 Goroutine 用来进行通信的管道。与水在管道中从一端流向另一端的方式类似,可以使用Channel从一端发送数据并从另一端接收数据。 声明Chan…...
java 编译 引用 jar 包进行编译和执行编译后的class文件
编译java文件 javac -encoding UTF-8 -Djava.ext.dirs./ -d . ./FtpTest.java 执行编译class文件 java -Djava.ext.dirs./ com.util.FtpTest com.util为包路径...
Linux系统之部署Tale个人博客系统
Linux系统之部署Tale个人博客系统 一、Tale介绍1.1 Tale简介1.2 Tale特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、部署Tale个人博客系统4.1 下载Tale源码4.2 查看Tale源码目录4.3 查看安装脚本内…...
【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础
系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...
算法通关村|黄金挑战|K个一组进行反转
K个一组进行反转 1.头插法 public ListNode reverseKGroup(ListNode head, int k) {ListNode dummyNode new ListNode(0);dummyNode.next head;ListNode cur head;// 计算链表长度int len 0;while (cur ! null) {len;cur cur.next;}// 计算有几组int n len / k;ListNod…...
【Android Studio】工程中文件Annotate with Git Blame 不能点击
问题描述 工程文件中想要查看代码提交信息但是相关按钮不可点击 解决方法 Android Studio -> Preferences -> Version Control-> 在Unregistered roots里找到你想要的工程文件 点击左上角➕号 然后右下角Apply即可...
Ant Design Vue
2222222222222...
ATA-P2010压电叠堆功率放大器-直流偏置对压电叠堆测试的重要性
随着科技的发展和应用领域的扩展,压电技术在许多领域中得到了广泛的应用。在压电器件的研究和开发过程中,压电叠堆测试是非常重要的一环。本文通过对功率放大器的直流偏置功能在压电叠堆测试中的应用进行了深入研究,探讨了功率放大器直流偏置…...
短视频矩阵系统搭建/源头----源码
一、智能剪辑、矩阵分发、无人直播、爆款文案于一体独立应用开发 抖去推----主要针对本地生活的----移动端(小程序软件系统,目前是全国源头独立开发),开发功能大拆解分享,功能大拆解: 7大模型剪辑法(数学阶乘ÿ…...
基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用
零、参考资料 1、https://github.com/cefsharp/CefSharp/wiki/Quick-Start-For-MS-.Net-5.0-or-greater 2、https://github.com/cefsharp/CefSharp/wiki/Quick-Start 3、https://github.com/cefsharp/CefSharp/wiki/General-Usage#javascript-integration 一、安装 Nuget 包…...
qt中怎么在鼠标停留的位置上显示该点的坐标位置
需要重写控件的mouseMoveEvent方法。 1、自定义一个QLabel控件,然后重写QLabel的mouseMoveEvent customlabel.h#include <QWidget> #include <QHBoxLayout> #include <QLabel>class CustomLabel : public QLabel {Q_OBJECT public:explicit Cus…...
两个list中实体某个属性值相同的实体和不同的实体
说明 有两个list,分别是newList 和 oldList,快速取出两个 newList 中某个属性值相同的实体和不同的实体 代码 import lombok.Data; import lombok.ToString;import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.str…...
Linux下利用Docker快速部署Kafka
1.摘要 在本文中,介绍了利用Docker安装Kafka的基础环境要求; 利用Docker安装zookeeper过程; 利用Docker安装Kafka过程;进入容器配置生产者和消费者过程; 演示生产者和消费者通讯; 故障排查方法。 2.基础环境准备 提前准备一台安装Linux系统的主机或虚拟机,我这里安装的是Ubu…...
竞赛 深度学习图像分类算法研究与实现 - 卷积神经网络图像分类
文章目录 0 前言1 常用的分类网络介绍1.1 CNN1.2 VGG1.3 GoogleNet 2 图像分类部分代码实现2.1 环境依赖2.2 需要导入的包2.3 参数设置(路径,图像尺寸,数据集分割比例)2.4 从preprocessedFolder读取图片并返回numpy格式(便于在神经网络中训练)2.5 数据预…...
jvm摘要
第 2 章 Java 内存区域与内存溢出异常 2.2 运行时数据区域 程序计数器-线程私有:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 程序计数器是唯一一个没有规定任何OutOfMemoryError 情况的区域。 Java 虚拟机栈-线程私有:用于执行Java …...
GZ035 5G组网与运维赛题第1套
2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项(高职组) 赛题第1套 赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通(35分) 子任务1:5G公共网络部署与调试(15分) 子任务2:5G室内与室外站点建设(20分) 竞赛模块2--5G公共网络运维与优化(3…...
MySQL数据xtrabackup物理备份方法
目录 一、物理备份的方式二、xtrabackup物理备份1.安装xtrabackup2.完整备份/恢复流程3.增量备份流程4.差异备份流程5.物理备份总结 一、物理备份的方式 1.完整备份 每次对数据进行完整的备份,即对整个数据库的备份、数据库结构和文件结构的备份,保存的…...
vue3 使用 elementUi: ./lib/theme-chalk/index.css is not exported from package
目录 1. 在 vue3 中使用 element-ui2. 如果启动报错:Module not found: Error: Package path ./lib/theme-chalk/index.css is not exported from package 1. 在 vue3 中使用 element-ui 在 vue3 中使用 element-ui,我们的流程一般是这样的:…...
[ROS系列]ORB_SLAM3错误版本(仅记录)
背景: 1、设备:pc;旭日派x3(后续会加上,目前只有pc) 2、环境:Ubuntu20.04;ROS2(Foxy) ros2机器人foxy版用笔记本摄像头跑单目orb_slam3-CSD…...
APP盾的防御机制及应用场景
移动应用(APP)在我们日常生活中扮演着越来越重要的角色,但随之而来的是各种网络安全威胁的增加。为了保障APP的安全性,APP盾作为一种专门设计用于防御移动应用威胁的工具得以广泛应用。本文将深入探讨APP盾的防御机制以及在不同应…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
