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。 并将其作为参数传递给hello
Goroutine。在14行号中。 我们正在从done
Channel接收数据。这行代码是阻塞的,这意味着在某个 Goroutine 将数据写入Channel之前done
,控件不会移动到下一行代码。因此,这消除了原始程序中存在的用于防止主 Goroutine 退出的需要time.Sleep
。
该代码行<-done
从完成的Channel接收数据,但不使用该数据或将该数据存储在任何变量中。这是完全合法的。
现在我们的main
Goroutine 被阻塞,等待完成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
。打印完此信息后,hello
Goroutine 将休眠 4 秒,在此期间,main
Goroutine 将被阻塞,因为它正在等待第 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仅在sendData
Goroutine 内部发送,但在主 Goroutine 中是双向的。该程序将打印10
为输出。
关闭Channel和Channel上的范围循环
发送方能够关闭Channel,以通知接收方不再有数据在Channel上发送。
接收者在从Channel接收数据时可以使用附加变量来检查Channel是否已关闭。
v, ok := <- ch
ok
如果通过成功发送到Channel的操作接收到该值,则上述语句为 true。如果ok
为 false,则意味着我们正在从封闭的Channel中读取数据。从关闭的Channel读取的值将是该Channel类型的零值。例如,如果Channel是一个int
Channel,那么从关闭的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
在上面的程序中,producer
Goroutine 将 0 到 9 写入chnl
Channel,然后关闭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
和calcCubes
Goroutine 使用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盾的防御机制以及在不同应…...

Unity性能优化一本通
文章目录 关于Unity性能优化一、资源部分:1、图片1.1、 图片尺寸越小越好1.2、使用2N次幂大小1.3、取消勾选Read/Write Enabled1.4、图片压缩1.5、禁用多余的Mip Map1.6、合并图集 2、模型2.1.限制模型面数2.2.限制贴图的大小2.3.禁用Read/Write Enables2.4.不勾选其…...

Mysql,SqlServer,Oracle获取库名 表名 列名
先看下需求背景: 获取某个数据源连接下所有库名,库下所有表名,表中所有字段 1.MySql 先说MySql吧,最简单 1.1获得所有数据库库名 这是一个mysql和sqlserver公用的方法,这里url不用担心数据库问题,他其实…...

errno变量和显示错误信息
一、errno Linux很多函数发生错误,只会返回-1。因此,我们只知道函数发生了错误,却不知道具体发生了什么错误。 因此Linux 系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型。 发生错误时,对…...

Centos 7 安装 Docker Enginee
文章目录 Centos 安装 Docker Enginee系统要求卸载旧版本使用 RPM 仓库设置 Docker 仓库安装 Docker Enginee升级 Docker Enginee 卸载 Docker Centos 安装 Docker Enginee 要在 Centos 安装 Docker Enginee,需要满足以下要求: 系统要求 CentOS 7Cent…...

通过js来实现用身份证号来判断性别和出生年月
html: <input type"text" id"shenfenzhenghao" oninput"hao()" placeholder"证件号"><input type"text" id"xingbie" disabled"disabled" placeholder"性别"><input type&qu…...

华为数通方向HCIP-DataCom H12-831题库(多选题:61-80)
第61题 在MPLS VPN中,为了区分使用相同地址空间的IPV4前缀,将IPV4的地址增加了RD值,下列选项描述正确的是: A、在PE设备上,每一个VPN实例都对应一个RD值,同一PE设备上,必须保证RD值唯一 B、RD可用于来控制VPN路由信息的发布 C、RD在传递过程中作为BGP的扩展团体性封装在…...

【T】03
A 【模板】快速幂 板子,略 #include<bits/stdc.h> #define ll long long using namespace std; ll a,p,k; int main() {scanf("%lld%lld%lld",&a,&p,&k);printf("%lld^%lld mod %lld",a,p,k);ll ans1,wa;a%k;while(p){if(p…...

VBA技术资料MF73:将Logo添加到页眉侧
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。我的教程一共九套,分为初级、中级、高级三大部分。是对VBA的系统讲解,从简单的入门,到…...

听GPT 讲Rust源代码--library/std(1)
std git:(master) ✗ tree.├── Cargo.toml├── benches│ ├── hash│ │ ├── map.rs│ │ ├── mod.rs│ │ └── set_ops.rs│ └── lib.rs├── build.rs├── src│ ├── alloc.rs│ ├── ascii.rs│ ├── backtrace│ │…...

Vue源码总结
1,根据vue工程package.json配置文件查看scripts命令,找到build命令执行的js文件 2,根据构建执行的js文件继续跟进,找到主入口文件 3,从主入口文件直接分析主干代码,追踪export的Vue对象 4,跟…...