[Golang] Channel

news/2024/9/18 16:05:41/ 标签: golang, 开发语言, 后端

[Golang] Channel

文章目录

  • [Golang] Channel
    • 什么是Channel
    • channel的初始化
    • channel的操作
    • 双向channel和单向channel
    • 为什么有channel
    • 有缓冲channel和无缓冲channle
    • channel做一把锁

从之前我们知道go关键字可以开启一个Goroutine,但是Goroutine之间的通信还需要另一个条件:channel

什么是Channel

官方定义:Channel are typed conduit through which you can send and receive values with the channel operator.

channel是一个可以收发数据的管道。

channel的初始化

var channel_name chan channel_type
var channel_name [size]chan channel_type//声明一个容量为size的channel

例如:

var ch1 chan int
var ch2 [1]chan int

但是声明后的channel,我们没有进行初始化为其分配空间,其值为nil,我们还需要使用make函数来对其初始化,之后才可以在程序中使用该管道。

channel_name = make(chan channel_type)
channel_name = make(chan channel_type, size)

例如:

ch1 := make(chan int)
ch2 := make(chan int, 1)//一个容量为size的channel

channel的操作

发送数据:

ch := make(chan int)	// 创建管道
ch <- 1					// 向管道发送数据
v := <-ch				// 从管道读取数据,并存储的变量v
close(ch)				// 关闭管道

注意:用完管道后,我们需要关闭管道close(ch),避免程序一直等待以及资源的浪费。但是关闭的管道仍然能读取数据,如果管道中还有数据,那就可以读到实际的值;如果管道中没有数据,此时读取的值就是该类型的零值,不会阻塞等待数据。

比如,例:

package mainimport ("fmt""time"
)func main() {ch := make(chan int, 5)ch <- 1close(ch)go func() {for i := 0; i < 5; i++ {v := <-chfmt.Println(v)}}()time.Sleep(2 * time.Second)
}

执行结果:

image-20240913234519220

创建一个缓存为5的int类型管道,向管道里写入一个1之后关闭管道。开启一个Goroutine从管道中读取数据,读5次,我们可以看到从第二次开始,读到的数据一直是0。

但是如果我们想要向管道中写入0呢?

一般采用:

1.判断读取:

package mainimport ("fmt""time"
)func main() {ch := make(chan int, 5)ch <- 1close(ch)go func() {for i := 0; i < 5; i++ {v, ok := <-ch 						// 判断式读取if ok {fmt.Println("数据读完了", v)} else {fmt.Println("数据没读完", v)}}}()time.Sleep(2 * time.Second)
}

执行结果:

image-20240913234937999

我们在读取数据时,加上了一个ok进行判断。ok为true时,读取的是正常值;ok为false,读取的是零值。

2.for range 读取

有时,我们的读取是不知道次数的,只是在channel中进行读取,有数据我们就读,直到管道关闭。

此时可以使用for range 读取管道中的数据

package mainimport ("fmt""time"
)func main() {ch := make(chan int, 5)ch <- 1ch <- 2ch <- 3close(ch)go func() {for i := 0; i < 5; i++ {for v := range ch {fmt.Println(v)}}}()time.Sleep(2 * time.Second)
}

执行结果:

image-20240913235317638

我们向管道中只写入了1,2,3,三个数据,之后就关闭了管道。Goroutine中也只能读到三个数据,然后管道被关闭了,Goroutine的for range循环也就退出了。

双向channel和单向channel

channel根据其功能又能分为双向channel和单向channel,双向channel既可以发送数据又可以接收数据,单向channel要么是发送数据,要么是接收数据。

单向读channel

var ch = make(chan int)
type ReadChannel = <-chan int	// 给 <-chan int取个别名
var rec ReadChannel = ch

单向写channel

var ch = make(chan int)
type SendChannel = chan<- int
var sec SendChannel = ch

读channel与写channel在定义时只是<-的位置不同,读在chan关键字前,写在chan关键字后。

使用示例:

package mainimport ("fmt""time"
)type ReadChannel = <-chan int
type SendChannel = chan<- intfunc main() {var ch = make(chan int)defer close(ch)go func() {var rec ReadChannel = chv := <-recfmt.Println(v)}()go func() {var sec SendChannel = chsec <- 100}()time.Sleep(2 * time.Second)
}

执行结果:

image-20240914000632202

创建一个读channel,一个写channel,向写channel中写入100,从读channel中读取数据。

为什么有channel

Golang中有个重要的思想:不以共享内存来通信,以通信来共享内存,channel就是其特点。

也就是说,协程之间可以利用channel来传递数据,以下例子可以看出父子协程是如何通过channel通信的:

package mainimport ("fmt""time"
)func sum(nums []int, ch chan int) {cnt := 0for _, v := range nums {cnt += v}ch <- cnt
}func main() {var ch = make(chan int)defer close(ch)nums := []int{-5, 4, -3, 2, -1} // 和为-3go func() {sum(nums[:len(nums)/2], ch)}()go sum(nums[len(nums)/2:], ch)m, n := <-ch, <-chfmt.Println(m, n, m+n)time.Sleep(2 * time.Second)
}

执行结果:

image-20240914001709564

有缓冲channel和无缓冲channle

之前初始化时,我们已经说明了channel分为有缓冲和无缓冲两种。

为了协程安全,有缓冲channel和无缓冲channle的内部都有一把锁来控制并发访问。

同时,channel底层一定有一个队列来存储数据。

无缓冲channel可以理解为同步模式,即写入一个,如果消费者不消费,写入就会阻塞

有缓冲channel可以理解为异步模式,即写入消息后,即使没有被消费,只要队列没有满,就可以继续写入。

image-20240914002245442

此时如果,channel满了,异步就会退化为同步,发送还是会阻塞。如果一个channel长期处于满队列状态,就没必要使用有缓冲channel了,直接有无缓冲即可。

所以大部分情况,有缓冲的channel一般用来做异步操作。

  • 无缓冲channel:适合用于严格同步的场景,比如两个Goroutine之间进行同步,要严格确保操作顺序。

例如,两个协程循环打印A、B

package mainimport ("fmt""sync""time"
)func main() {var wg sync.WaitGroupwg.Add(2)var Ach = make(chan int)var Bch = make(chan int)defer close(Ach)defer close(Bch)go PrintA(&wg, Ach, Bch)go PrintB(&wg, Ach, Bch)Ach <- 1 // 从A启动wg.Wait()
}func PrintA(wg *sync.WaitGroup, Ach chan int, Bch chan int) {defer wg.Done()for {<-Achfmt.Println("A")time.Sleep(time.Second)Bch <- 1 // 通知打印B}
}func PrintB(wg *sync.WaitGroup, Ach chan int, Bch chan int) {defer wg.Done()for {<-Bchfmt.Println("B")time.Sleep(time.Second)Ach <- 1 // 通知打印A}
}
  • 有缓冲channel:适合一定程度的异步处理的场景,比如提高吞吐量,减少Goroutine之间的阻塞。

channel做一把锁

因为缓冲队列满了之后,再往channel中写数据就会被阻塞,所以我们可以把channel当一把锁来使用

package mainimport ("fmt""time"
)func main() {ch := make(chan bool, 1)var sum intfor i := 0; i < 1000; i++ {go add(ch, &sum)}time.Sleep(2 * time.Second)fmt.Println("sum = ", sum)
}func add(ch chan bool, sum *int) {ch <- true*sum = *sum + 1<-ch
}

执行结果:

image-20240914004020661

如果不用锁,循环次数一多就会出现并发问题。


http://www.ppmy.cn/news/1525719.html

相关文章

ros2中使用launch.xml启动时,怎么在命令行里设置参数,或者加载参数文件(params.yaml)

在ROS2中使用launch.xml启动时&#xff0c;可以通过命令行设置参数或加载参数文件&#xff08;如params.yaml&#xff09;。以下是具体的方法&#xff1a; 1. 在命令行中设置参数 你可以在运行ros2 launch命令时直接设置参数&#xff0c;使用key:value的语法。例如&#xff1…

SSL/TLS

SSL/TLS 握手的全部步骤 客户端 Hello&#xff08;Client Hello&#xff09; 客户端向服务器发送 Client Hello 消息&#xff0c;包含以下信息&#xff1a; 支持的 SSL/TLS 版本&#xff08;例如 TLS 1.2 或 TLS 1.3&#xff09;支持的加密套件&#xff08;cipher suites&#…

golang学习笔记22——golang微服务中数据竞争问题及解决方案

推荐学习文档 golang应用级os框架&#xff0c;欢迎star基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学…

Java学习Day41:骑龙救!(springMVC)

springMVC与sevlet都是对应表现层web的&#xff0c;但是越复杂的项目使用SpringMVC越方便 基于Java实现MVC模型的轻量级web框架 目标&#xff1a; 小案例&#xff1a; 1.导入依赖 spring-context: 提供 Spring 框架的核心功能&#xff0c;如依赖注入、事件发布和其他应用上…

Linux环境基础开发工具---vim

1.快速的介绍一下vim vim是一款多模式的编辑器&#xff0c;里面有很多子命令&#xff0c;来实现代码编写操作。 2.vim的模式 vim一共有三种模式&#xff1a;底行模式&#xff0c;命令模式&#xff0c;插入模式。 2.1vim模式之间的切换 2.2 谈论常见的模式---命令模式&#xf…

为什么H.266未能普及?EasyCVR视频编码技术如何填补市场空白

H.266&#xff0c;也被称为Versatile Video Coding&#xff08;VVC&#xff09;&#xff0c;是近年来由MPEG&#xff08;Moving Picture Experts Group&#xff09;和ITU&#xff08;International Telecommunication Union&#xff09;联合开发并发布的新一代国际视频编码标准…

【云原生安全篇】一文掌握Harbor集成Trivy应用实践

【云原生安全篇】一文掌握Harbor集成Trivy应用实践 目录 1 概念 1.1 什么是 Harbor 和 Trivy&#xff1f; 1.1.1 Harbor 1.1.2 Trivy 1.2 Harbor 与 Trivy 的关系 Trivy 在 Harbor 中的作用&#xff1a; 1.3 镜像扫描工作流程 2 实战案例&#xff1a;在Harbor 配置 Trivy …

Android 签名、空包签名 、jarsigner、apksigner

jarsigner是JDK提供的针对jar包签名的通用工具, 位于JDK/bin/jarsigner.exe apksigner是Google官方提供的针对Android apk签名及验证的专用工具, 位于Android SDK/build-tools/SDK版本/apksigner.bat jarsigner&#xff1a; jarsigner签名空包执行的命令&#xff1a; jar…

【系统架构师】-论文-2024-2009年系统架构师历年论文题目

2024年5月 大数据Lambda架构的应用与分析 云原生云上DevOps运维应用与分析 模型驱动软件开发方法与应用 论单元测试在软件回归测试中的应用和分析 2023年 论面向对象设计的应用与实现 论多数据源集成的应用与实现 论软件可靠性模型的设计与实现 论边缘计算技术的设计与实现 …

openssh移植:精致的脚本版

前置文章&#xff1a; busybox移植&#xff1a;全能脚本版-CSDN博客 zlib交叉编译-CSDN博客 openssl移植:精致的脚本版-CSDN博客 源码下载 官网&#xff1a;http://www.openssh.com/ 下载了一个很新的版本 ftp://mirrors.sonic.net/pub/OpenBSD/OpenSSH/portable/openss…

android API、SDK与android版本

随着 Android 系统的不断更新&#xff0c;API Level 也会随之增加。每个新的 API Level 都引入了新的功能、改进旧的功能&#xff0c;或者弃用了旧的 API。开发者在开发应用时&#xff0c;需要指定目标 API Level&#xff0c;也就是应用最低支持的 Android 版本。 API Level 与…

C++速通LeetCode简单第9题-二叉树的最大深度

深度优先算法递归&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right…

Java面试题·解释题·单例模式、工厂模式、代理模式部分

系列文章目录 Java面试题解释题JavaSE部分 Java面试题解释题框架部分 Java面试题解释题单例模式、工厂模式、代理模式部分 文章目录 系列文章目录前言一、设计模式1. 单例模式1.1 单例模式的定义1.2 单例模式的实现方法 2. 工厂模式2.1 工厂模式的定义2.2 工厂模式的实现方法2…

JAVA毕业设计175—基于Java+Springboot+vue3的医院预约挂号管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的医院预约挂号管理系统(源代码数据库)175 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、医生、管理员三种角色 1、用户&#x…

使用Python自动抓取亚马逊网站商品信息

全量数据抓取不现实&#xff0c;但可以自动化、小批量采集亚马逊数据&#xff0c;现在可用的工具也非常多&#xff0c;包括Python以及一些专门的爬虫软件&#xff0c;我用过几个比较好入手的&#xff0c;像web scraper、八爪鱼、亮数据。 比如亮数据爬虫&#xff0c;它提供数据…

Qt C++ Udp相关知识学习(一)

文章目录 udp 单播消息,是什么意思特点:使用场景:例子:udp 广播消息,是什么意思特点:使用场景:示例:参考udp 单播消息,是什么意思 UDP 单播消息(UDP unicast)是指使用用户数据报协议(UDP)通过网络发送消息的过程,消息的接收者是单个特定的目标设备或IP地址。 特…

uniapp与webview进行数据通信

uniapp与webview进行数据通信&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width,initial-scale1,minimum-scale1,maximum-scale1,user-scalableno"…

Redis简介、常用命令及优化

文章目录 一、关系数据库​​与非关系型数据库概述1. 关系型数据库2. 非关系型数据库3.关系数据库与非关系型数据库区别 二、Redis简介1.Redis的单线程模式2.Redis 优点3.Redis 缺点 三、安装redis四、Redis 命令工具五、Redis 数据库常用命令六、Redis 多数据库常用命令七、Re…

【Linux取经之路】软件包管理器yum编辑器vim及其配置

目录 软件包管理器yum 1、什么是软件包 2、关于lrzsz 3、安装软件 4、查看软件 5、卸载软件 编辑器——vim 1、vim的基本概念 2、vim的基本操作 3、vim普通模式命令集 4、底行模式命令集 5、vim的配置 配置sodu权限 软件包管理器yum 1、什么是软件包 在Linux下…

大顶堆+动态规划+二分

前言&#xff1a;我们这一题需要分类讨论 对于我们左边和右边的我们需要预处理 有点类似反悔堆的做法&#xff0c;得出i之前取出 m 个元素代价最小&#xff0c;并且这个代价一定是递减的&#xff08;可以推导一下&#xff09; 题目地址 #include<bits/stdc.h> using name…