【Golang】Go语言编程思想(六):Channel,第一节,介绍Channel

news2024/12/12 19:43:25

Channel

下面的几个例子将会展示如何定义一个 channel:

func chanDemo() {
	var c chan int // chan int 的含义是, c 是一个 channel, 里面的内容是 int
	// 上面的声明语句将会创建一个 nil channel, c == nil, 它的作用将在 select 当
	// 中体现
}

创建一个非 nil 的 channel 的方法是使用内建的 make 函数:

package main

import "fmt"

func chanDemo() {
	c := make(chan int)
	go func() {		 // 由于 channel 是 goroutine 之间的通道
		for {		 // 因此应该建立一个 goroutine 不断收数据
			n := <-c // 从 channel 收数据
			fmt.Println(n)
		}
	}()
	c <- 1 // 向 channel 发数据
	c <- 2 // 向 channel 发数据
}

func main() {
	chanDemo()
}

Channel 也是 Golang 当中的一等公民

在函数式编程的学习过程中,我们已经看到,函数是 Golang 的一等公民,它可以作为参数,也可以作为返回值。而 Channel 同样也是 Golang 的一等公民,它同样可以作为参数也可以作为返回值。

为了展示其用法,我们对上面的代码进行修改,首先将上面代码当中的匿名函数单独定义为一个函数:

package main

import (
	"fmt"
	"time"
)

func worker(c chan int) { // channel 作为参数
	for {
		n := <-c // 从 channel 收数据
		fmt.Println(n)
	}
}

func chanDemo() {
	c := make(chan int)
	go worker(c)
	c <- 1 // 向 channel 发数据
	c <- 2 // 向 channel 发数据
	time.Sleep(time.Second)
}

func main() {
	chanDemo()
}

其输出仍然是 1 和 2。

为了进一步体现 channel 是一等公民,使用 var 声明一个存储 10 个 chan int 型 channel 的数组,并在循环中使用内建的 make 对每个 chan int 进行构造,并将 chan int 分发给 10 个 workers:

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) { // channel 作为参数
	for {
		n := <-c // 从 channel 收数据
		fmt.Printf("Worker %d received %c\n", id, n)
	}
}

const worker_num int = 10

func chanDemo() {
	var channels [10]chan int // 建立一个包含 10 个 chan int 的数组 channels
	for i := range worker_num {
		channels[i] = make(chan int) // 使用内建的 make 构造每一个 channel
		go worker(i, channels[i])    // 将 10 个 chan int 分发给 10 个 worker
	}

	for i := range worker_num {
		channels[i] <- 'a' + i
	}
	time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
}

输出的结果如下,因操作设备而异:

Worker 0 received a
Worker 2 received c
Worker 1 received b
Worker 3 received d
Worker 9 received j
Worker 4 received e
Worker 5 received f
Worker 6 received g
Worker 7 received h
Worker 8 received i

channel 除了可以作为参数、可以存储在数组当中,channel 还可以作为返回值:

package main

import (
	"fmt"
	"time"
)

func createWorker(id int) chan<- int {	// 返回值 chan int 用于发数据, 因此添加 <- 告知使用者
	// 实际上这个函数的执行相当快: 新建一个 chan int, 并开启一个由匿名函数构成的 goroutine, 之后就返回建立的 chan int
	// goroutine 将会一直运行下去
	c := make(chan int) // 在函数中创建 channel, 它也是返回值
	go func() {         // 开一个 goroutine, 来接受 channel 得到的内容
		for {
			n := <-c // 从 channel 收数据
			fmt.Printf("Worker %d received %c\n", id, n)
		}
	}()
	return c
}

const worker_num int = 10

func chanDemo() {
	var channels [10]chan<- int // 建立一个包含 10 个 chan int 的数组 channels
	for i := range worker_num {
		channels[i] = createWorker(i)
	}

	for i := range worker_num {
		channels[i] <- 'a' + i
	}
	for i := range worker_num {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
}

输出的结果为:

Worker 0 received a
Worker 0 received A
Worker 4 received e
Worker 9 received j
Worker 6 received g
Worker 7 received h
Worker 8 received i
Worker 2 received c
Worker 1 received b
Worker 1 received B
Worker 5 received f
Worker 3 received d
Worker 3 received D
Worker 2 received C
Worker 6 received G
Worker 4 received E
Worker 5 received F
Worker 7 received H
Worker 8 received I
Worker 9 received J

bufferedChannel

之前我们提到过,建立一个 channel 之后,如果向 channel 当中写入数据,则应该有一个 goroutine 负责接收数据(即,在 go func 的函数体内进行 n := <- channel)。

比如,运行下述代码:

func bufferedChannel() {
	c := make(chan int)
	c <- 1
}

func main() {
	bufferedChannel()
}

将会产生如下的错误信息:
在这里插入图片描述
可以为 channel 加入一个缓冲区,来解决上述问题:

func bufferedChannel() {
	c := make(chan int, 3) // 在 make 当中进一步输入参数 3, 代表当前缓冲区的大小设置为 3
	c <- 1
	c <- 2
	c <- 3
}

func main() {
	bufferedChannel()
}

此时程序可以正常运行,不会 deadlock,如果进一步发送 4,才会 deadlock。

现在输出 channel 当中的数据:

func worker(id int, c chan int) {
	for {
		n := <-c // 从 channel 收数据
		fmt.Printf("Worker %d received %d\n", id, n)
	}
}

func bufferedChannel() {
	c := make(chan int, 3) // 在 make 当中进一步输入参数 3, 代表当前缓冲区的大小设置为 3
	go worker(0, c)
	c <- 1
	c <- 2
	c <- 3
	c <- 4
	time.Sleep(time.Millisecond)
}

func main() {
	bufferedChannel()
}

输出的结果为:

Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4

我们还想要知道,什么时候 channel 当中的数据发完了。应该由发送方来通知接收方,没有新的数据要发送了:

func channelClose() {
	c := make(chan int)
	go worker(0, c)
	c <- 1
	c <- 2
	c <- 3
	c <- 4
	close(c) // 告诉接受方, 数据发完了
	time.Sleep(time.Millisecond)
}

func main() {
	channelClose()
}

输出的结果如下:

Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
Worker 0 received 0
... (Worker 0 received 0 repeated ...)

当 channel 通过 close 通知 goroutine 没有数据要发送了的时候,goroutine 仍然会接收数据,只不过接受的数据是零值。在 goroutine 接收 channel 的数据时,可以使用两个返回值 n 和 ok 来判断 channel 是否 close,因此我们对 worker 进行修改:

func worker(id int, c chan int) {
	for {
		n, ok := <-c // 使用两个值来判断 channel 是否 close
		if !ok {
			break
		}
		fmt.Printf("Worker %d received %d\n", id, n)
	}
}

此时的输出为:

Worker 0 received 1
Worker 0 received 2
Worker 0 received 3
Worker 0 received 4

另一种判断 channel 是否 close 的方式是在 for 循环使用:

func worker(id int, c chan int) {
	for n := range c {
		fmt.Printf("Worker %d received %d\n", id, n)
	}
}

可以得到相同的结果。

总结上述学习到的内容:

  • 本节介绍了 channel 及其定义、发送、接收数据的方法;
  • 如何定义以及使用 bufferd channel,在 make 内置方法构造 channel 时显式地指定 buffer 的大小即可;
  • 在接收数据时使用 range 可以方便地判断 channel 是否关闭。

为什么使用 Channel?原因是:不要通过共享内存来通信,而是通过通信来共享内存。

通过共享内存来通信的典型例子是,两个线程之间通过一个 flag 变量来判断事务是否完成。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么获取Java高并发经验与系统设计技能?

如何获得高并发经验&#xff1f; 这是系统邀请我回答的一个问题&#xff0c;由此也引发了我的一些思考&#xff1a;为什么人人都想要获得高并发经验&#xff1b;想拥有高并发系统设计技能&#xff1f; 其原因LZ认为主要有以下三点&#xff1a; 涨薪&#xff1a;有高并发系统设…

Spherical Harmonics (SH)球谐函数的原理及应用【3DGS】

Spherical Harmonics &#xff08;SH&#xff09;球谐函数的原理及应用【3DGS】 前言球谐函数&#xff08;Spherical Harmonics, SH&#xff09;球谐函数不同阶的表达式以及有什么不同&#xff1f;具体介绍球谐函数基函数球谐函数 前言 高斯泼溅Gaussian Splatting (GS) GS 模…

Java版-图论-拓扑排序与有向无环图

拓扑排序 拓扑排序说明 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列…

如何在 Odoo18 视图中添加关联数据看板按钮 | 免费开源ERP实施诀窍

文 / 开源智造 Odoo亚太金牌服务 引言 关联数据看板按钮乃是 Odoo 当中的一项强效功能&#xff0c;它容许用户顺遂地访问相关记录&#xff0c;或者直接从模型的表单视图施行特定操作。它们为用户给予了对重要信息的疾速访问途径&#xff0c;并简化了工作流程&#xff0c;由此…

TCP客户端服务器端通信(线程池版)

1、什么是监听套接字&#xff0c;和UDP相比&#xff0c;TCP为什么文件描述符变多了&#xff1f; 在网络编程中&#xff0c;TCP和UDP是两种常见的传输协议&#xff0c;它们之间最大的不同之一在于连接的管理方式。为了更好地理解这个区别&#xff0c;我们可以用一个生动的比喻来…

【Linux】通过crond服务设置定时执行shell脚本,实际执行时间却延迟了8小时

一、问题描述 通过使用crond服务设置定时任务&#xff0c;在每天凌晨的2:00执行脚本&#xff0c;但检查结果时发现&#xff0c;实际执行时间却在上午10点。 检查shell脚本执行结果发现&#xff0c;实际执行脚本时间在上午10:00&#xff0c;延迟了8小时。 检查系统时间&#xf…

Git基础笔记

目录 1.Git 常用命令 2.Git 分支操作 3.远程仓库操作 Git 概述 Git 是一个免费的、开源的 分布式版本控制系统 &#xff0c;可以快速高效地处理从小型到大型的各种 项目 1.Git 常用命令 1.设置用户签名 git config --global user.name 用户名 2.设置用户签名 git config…

PADS系列:绘制RTL8306原理图的过程

大家好&#xff0c;我是山羊君Goat。 在所有相关的元件都被创建到了原理图库之后&#xff0c;就可以正式开始原理图的绘制了。不过绘制过程中也是会按照一定的顺序来进行的&#xff0c;这样可以达到事半功倍的效果。 首先就是主芯片的放置&#xff0c;这里有三个主芯片&#x…

GCP Case:MountKirk Games

游戏后端 根据游戏活动动态放大或缩小。 连接到托管的nos0l数据库服务。 运行定制的linux发行版。 游戏分析平台 根据游戏活动来扩大或缩小规模直接处理来自游戏服务器的传入数据。 处理由于移动网络缓慢而迟到的数据。 通过sql查询来访问至少10tb的历史数据 处理由用户…

OpenCV相机标定与3D重建(10)眼标定函数calibrateHandEye()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算手眼标定&#xff1a; g T c _{}^{g}\textrm{T}_c g​Tc​ cv::calibrateHandEye 是 OpenCV 中用于手眼标定的函数。该函数通过已知的机器人…

【CSS in Depth 2 精译_072】第 12 章 CSS 排版与间距概述 + 12.1 间距设置(上):究竟该用 em 还是 px

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 12 章 CSS 排版与间距】 ✔️ 12.1 间距设置 ✔️ 12.1.1 使用 em 还是 px ✔️12.1.2 对行高的深入思考12.1.3 行内元素的间距设置 文章目录 第 12 章 排版与间距…

数据结构代码归纳

1.线性表 线性表的顺序表示 定义与初始化 typedef struct SqList{ElemType data[MaxSize];//ElemType *data 开动态数组 int length; }Sqlist; void InitList(SqList &L){L.length0;//若静态数组//若动态数组 //L.data(ElemType*)malloc(sizeof(ElemType)*MaxSize); }…

数据结构 (36)各种排序方法的综合比较

一、常见排序方法分类 插入排序类 直接插入排序&#xff1a;通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。希尔排序&#xff1a;是插入排序的一种改进版本&#xff0c;先将整个待排序的记录序列分割成为…

SpringMVC全局异常处理

一、Java中的异常 定义&#xff1a;异常是程序在运行过程中出现的一些错误&#xff0c;使用面向对象思想把这些错误用类来描述&#xff0c;那么一旦产生一个错误&#xff0c;即创建某一个错误的对象&#xff0c;这个对象就是异常对象。 类型&#xff1a; 声明异常&#xff1…

【高中生讲机器学习】28. 集成学习之 Bagging 随机森林!

创建时间&#xff1a;2024-12-09 首发时间&#xff1a;2024-12-09 最后编辑时间&#xff1a;2024-12-09 作者&#xff1a;Geeker_LStar 嘿嘿&#xff0c;你好呀&#xff01;我又来啦~~ 前面我们讲完了集成学习之 Boooooosting&#xff0c;这篇我们来看看集成学习的另一个分支…

springSecurity权限控制

权限控制&#xff1a;不同的用户可以使用不同的功能。 我们不能在前端判断用户权限来控制显示哪些按钮&#xff0c;因为这样&#xff0c;有人会获取该功能对应的接口&#xff0c;就不需要通过前端&#xff0c;直接发送请求实现功能了。所以需要在后端进行权限判断。&#xff0…

李飞飞的生成式3D场景,对数字孪生的未来影响几何?

大家好&#xff0c;我是日拱一卒的攻城师不浪&#xff0c;致力于技术与艺术的融合。这是2024年输出的第47/100篇文章。 前言 这两天&#xff0c;AI界的教母李飞飞团队重磅发布了空间智能生成式AI大模型。 仅通过一张图片就能够生成一个可操作和交互的3D空间场景。 空间智能的…

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功 我们在开发AI-Agent智能体时&#xff0c;通常会使用提示词工程设置场景的带入&#xff0c;在实际项目中会有很多场景&#xff0c;如果所有提示词都放一起就会超过Token限制&#xff0c;则不得不拆…

OSG开发笔记(三十七):OSG基于windows平台msvc2017x64编译器官方稳定版本OSG3.4.1搭建环境并移植Demo

​若该文为原创文章&#xff0c;未经允许不得转载 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/144258047 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 长沙红胖子Qt…

《毛泽东思想和中国特色社会理论概述》课程报告Latex版本

所需要的图片: 源码(可运行): \documentclass[12pt]{article} \usepackage{ctex} \usepackage{graphicx} \usepackage{booktabs} \usepackage{titlesec} \usepackage{geometry} \usepackage{float} \usepackage{tabularx} \usepackage{enumitem} …