Go 知识chan

Go 知识chan

  • 1. 基本知识
    • 1.1 定义
    • 1.2 操作
    • 1.3 操作限定
    • 1.4 chan 读写
  • 2. 原理
    • 2.1 数据结构
    • 2.2 环形队列
    • 2.3 等待队列
    • 2.4 类型消息
    • 2.5 读写数据
    • 2.6 关闭chan
  • 3. 使用
    • 3.1 操作符使用
    • 3.2 select
    • 3.3 for-range

https://a18792721831.github.io/

1. 基本知识

chan是go里面里面提供的语言层面的协程间的通信方式,用于并发通信。

1.1 定义

  • 变量声明: var ch chan int // 声明一个int型的chan
    这种方式声明的chan,值为nil。并且每一种chan只能有一种类型。
  • make声明: ch1 := make(chan int)// 无缓存的chanch2 := make(chan int, 4)// 有缓冲的chan

1.2 操作

操作符<-表示数据流向,当chan在<-左边,表示数据流向chan,也就是向chan写入数据;当chan在<-右边,表示数据流出chan,
也就是从chan读取数据。

func TestChanOne(t *testing.T) {var ch chan intch = make(chan int) // 无缓冲chango func() {         // 创建一个协程,不断从chan中读取数据<-ch}()ch <- 1 // 写入数据,因为没有缓冲,如果没有上面那句,这句会阻塞var ch1 chan intch1 = make(chan int, 1) // 有缓冲chanch1 <- 2                // 因为有缓存,所以这句不会阻塞,但是因为缓冲长度是1,所以在写入就会阻塞fmt.Printf("len=%d, cap=%d\n", len(ch1), cap(ch1))fmt.Println(<-ch1) // 读取数据,因为有缓冲,并且有数据,所以不会阻塞
}

执行结果:
在这里插入图片描述

1.3 操作限定

默认的chan为双向可读写,chan在函数间传递的时候,可以使用操作符限制chan的读写。

func ChanRW(ch chan int) {ch <- 1<-ch
}func ChanR(ch <-chan int) {ch <- 1<-ch
}func ChanW(ch chan<- int) {ch <- 1<-ch
}

在这里插入图片描述在这里插入图片描述

1.4 chan 读写

chan无缓冲时,从chan读取数据会阻塞,直到有协程向chan写入数据。向chan写入数据也会阻塞,直到有协程读取数据。
chan有缓冲时,从chan读取数据,如果缓冲区中没有数据,那么也会阻塞,直到有协程写入数据。向chan写入数据,如果缓冲区已经满了,也会阻塞,直到有协程从chan中读取数据。
对于nil的chan,不管是读还是写,都是永久阻塞。 – 这个一定注意。
使用内置函数close可以关闭chan,尝试向关闭的chan写入数据会触发panic,但是仍然可以读取。
chan读取最多可以给两个变量赋值:

  i := <- chj, ok := <- ch

第一个变量表示读取的值,第二个变量表示是否成功读取了数据。第二个变量不表示chan是否关闭。
对于关闭的有缓冲的chan存在两种情况:1. 缓冲区中没有数据; 2. 缓冲区中有数据。
对于有缓冲的chan并且用内置函数close进行关闭,在继续读取,针对缓冲区中没有数据,读取的第一个值是该类型的零值,第二个值为false,表示没有成功读取数据。
如果缓冲区中存在数据,那么第一个值是缓冲区中的第一个数据,第二个值是true,表示正确读取到数据。

也就是说,只有缓冲区中没有数据了,第二个值才能代表chan已经关闭。
当chan没有缓冲区的时候,第二个值可以认为是chan的关闭状态。

2. 原理

2.1 数据结构

在源码包的src/runtime/chan.go中定义了chan的结构:

type hchan struct {qcount   uint           // 当前队列中剩余的元素个数dataqsiz uint           // 环形队列的长度,即可以存放的元素个数buf      unsafe.Pointer // 环形队列指针elemsize uint16         // 每个元素的大小closed   uint32         // 关闭状态elemtype *_type         // 元素类型sendx    uint           // 写入位置recvx    uint           // 读取位置recvq    waitq          // 阻塞读协程队列sendq    waitq          // 阻塞写协程队列lock     mutex          // 互斥锁
}

可以看出,一个chan基本上由数据缓冲,类型信息,和协程等待队列组成。

2.2 环形队列

环形队列主要是存储缓冲数据的,其实实现一个缓冲队列非常简单,只需要一个数组就能实现,每次数组指针到达最后一个元素,那么就重置为0即可。

	var data []intdata = make([]int, 10)for i := 0; i < 100; i++ {data[i] = iif i == len(data)-1 {i = 0}}

上面的if就是实现了环形队列,所以名字叫做环形队列,实际上可能是线性的结构。
在chan中使用双指针实现读取和写入数据,读指针表示读取的元素位置,写指针表示写入的元素位置。
当读指针等于写指针的时候,表示数据读取完毕,整个队列为空。
当写指针等于读指针的时候,表示队列满,无法写入。

2.3 等待队列

因为同一个chan可以传给多个协程,就会出现多个协程等待读取的情况;对于写数据也是相同的,那么此时就会根据先后顺序,组成等待队列,等待的协程组成了读阻塞队列和写阻塞队列。
当对chan进行写操作,那么唤醒读队列的协程;当对chan进行读操作,那么唤醒写队列的协程。
对于chan进行写操作,如果读协程队列为空,那么写操作阻塞;对于chan进行读操作,如果写协程队列为空,那么读操作阻塞。

需要注意,上述情况还有一个附件条件:无缓冲chan,如果是有缓冲chan,那么对于写操作,缓冲区满,对于读操作,缓冲区空。

2.4 类型消息

在chan的定义中,保存了类型消息和类型长度,类型消息主要是传递过程中赋值,类型长度主要是计算缓冲区中元素的位置。

2.5 读写数据

对于chan读写数据,实际上就是数据进入缓冲区和读出缓冲区的操作。
在实现上则存在一定的技巧:
写入数据时,如果读协程等待队列不为空,那么直接将数据给读协程等待队列的第一个协程即可,不进行数据缓冲区的数据写入,然后在唤醒,最后读取的操作,避免了数据的额外处理。
同样的,读取数据时,需要甄别一下,因为chan本质上是队列,需要符合先入先出的规则,在写入数据时,因为chan为空,所以不管有没有缓冲区,都可以直接将数据交给等待的读取协程。
但是在读取数据的时候,如果存在缓冲区,那么就必须从缓冲区中读取,读取数据后,环形写入协程等待队列,将数据写入缓冲区的队尾。
只有无缓冲区的chan,才可以直接从写等待协程队列中唤醒协程,并将数据直接交给读取协程。

2.6 关闭chan

关闭chan的时候,会将读等待队列和写等待队列的全部协程都唤醒。
对于读等待的协程,赋值该类型的零值,并且将第二个值赋值为false。

需要注意,对于chan,不会同时既存在读等待队列协程,又存在写等待队列协程。(长时间同时,瞬时的同时可能存在)

对于写等待的协程,直接触发panic,向close的chan写入数据,直接panic。
除此之外,这些操作也会panic:

  • 关闭nil的chan
  • 关闭已经关闭的chan
  • 向已经关闭的chan写入数据

3. 使用

3.1 操作符使用

使用<-进行操作,同时也可以限制在当前函数范围内chan的操作方式,读还是写,还是读写。

3.2 select

对于chan的使用,不管是读取还是写入,非常导致阻塞,但是很多时候,我们又不希望整个程序阻塞,因为是并发的,可能当前chan没有数据,但是别的chan可能有数据,那么就可以在等待一个chan的时候,处理其他chan的数据,增加程序的性能。
使用select就可以实现监控多个chan。

	sayOne := make(chan string)sayTwo := make(chan string)// 每两秒发送 onego func() {for {sayOne <- "one"time.Sleep(2 * time.Second)}}()// 每两秒发送 twogo func() {for {sayTwo <- "tow"time.Sleep(2 * time.Second)}}()for {// 如果 select 中的任何 case 都没有触发,那么 select 本身是会阻塞的,直到某个 case 触发select {case msg := <-sayOne:fmt.Println(msg)case msg := <-sayTwo:fmt.Println(msg)// 但是因为 select 触发的 case 是随机的,所以这里可能并不会正确执行第三个case 	case <-time.After(10 * time.Second):return}}

在这里插入图片描述

要想按照程序10秒后退出,可以使用定时器进行超时退出:

	// 设置超时time.AfterFunc(10*time.Second, func() {os.Exit(0)})

在这里插入图片描述

除此之外,还可以使用default进行计时,然后进行超时退出操作。

	sayOne := make(chan string)sayTwo := make(chan string)// 每两秒发送 onego func() {for {sayOne <- "one"time.Sleep(2 * time.Second)}}()// 每两秒发送 twogo func() {for {sayTwo <- "tow"time.Sleep(2 * time.Second)}}()// 设置超时//time.AfterFunc(10*time.Second, func() {//	os.Exit(0)//})tm := 0for {// 如果 select 中的任何 case 都没有触发,那么 select 本身是会阻塞的,直到某个 case 触发select {case msg := <-sayOne:fmt.Println(msg)case msg := <-sayTwo:fmt.Println(msg)// 但是因为 select 触发的 case 是随机的,所以这里可能并不会正确执行第三个case//case <-time.After(10 * time.Second)://	returndefault:if tm == 10 {return}time.Sleep(time.Second)tm++}}

在这里插入图片描述

3.3 for-range

使用<-操作符可以读取数据,除了使用<-操作符,还可以使用for-range进行数据的读取,就像读取一个数组一样。

	ch := make(chan int, 10)for i := 0; i < 10; i++ {ch <- i}for i := range ch {fmt.Println(i)}

在这里插入图片描述

使用for-range还有个好处,就是读取的数据都是成功的,不用去考虑数据是否读取成功,同时也不用考虑当chan关闭,当chan关闭后,for-range也能正确的处理。

	ch := make(chan int, 10)for i := 0; i < 10; i++ {ch <- i}// 5秒后关闭chantime.AfterFunc(5*time.Second, func() {close(ch)})// 每一秒读取一个元素for i := range ch {fmt.Println(i)time.Sleep(time.Second)}

在这里插入图片描述

即使第5秒的时候,chan被关闭了,也可以正确的读取全部的数据。

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

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

相关文章

源码篇--Redis 五种数据类型

文章目录 前言一、 字符串类型&#xff1a;1.1 字符串的编码格式&#xff1a;1.1.1 raw 编码格式:1.1.2 empstr编码格式:1.1.3 int 编码格式:1.1.4 字符串存储结构展示: 二、 list类型&#xff1a;2.1 List 底层数据支持&#xff1a;2.2 List 源码实现&#xff1a;2.3 List 结构…

微信小程序-04

rpx&#xff08;responsive pixel&#xff09;是微信小程序独有的&#xff0c;用来解决屏适配的尺寸单位。 import 后跟需要导入的外联样式表的相对路径&#xff0c;用 ; 表示语句结束。 定义在 app.wxss 中的样式为全局样式&#xff0c;作用于每一个页面。 在页面的 .wxss 文…

独享http代理安全性是更高的吗?

不同于共享代理&#xff0c;独享代理IP为单一用户提供专用的IP&#xff0c;带来了一系列需要考虑的问题。今天我们就一起来看看独享代理IP的优势&#xff0c;到底在哪里。 我们得先来看看什么是代理IP。简单来说&#xff0c;代理服务器充当客户机和互联网之间的中间人。当你使用…

幻兽帕鲁一键开私服?超简单小白教程一看就会!

如何自建幻兽帕鲁服务器&#xff1f;基于阿里云服务器搭建幻兽帕鲁palworld服务器教程来了&#xff0c;一看就懂系列。本文是利用OOS中幻兽帕鲁扩展程序来一键部署幻兽帕鲁服务器&#xff0c;阿里云服务器网aliyunfuwuqi.com分享官方基于阿里云服务器快速创建幻兽帕鲁服务器教程…

Python学习之路-Django基础:PythonWeb

Python学习之路-Django基础:PythonWeb Python Web 框架要点 处理流程 图片来源于未来的小牛的CSDN博客 意义 用于搭建Web应用程序&#xff0c;免去不同Web应用相同代码部分的重复编写&#xff0c;只需关心Web应用核心的业务逻辑实现 Web应用程序的本质 接收并解析HTTP请求…

HCIA学习作业三

要求&#xff1a; 拓扑图&#xff1a; <AR1>ping 5.5.5.1 <AR1>display ip interface brief <AR1>display ip routing-table <AR1>display ip routing-table protocol static <AR2>ping 5.5.5.1 <AR2>display ip interface brief <…

Linux实现:从倒计时到进度条

文章目录 1.回车与换行2.缓冲区的概念3.倒计时4.进度条(第一版无应用场景)5.进度条(第二版有应用场景) 1.回车与换行 2.缓冲区的概念 强制刷新可以使用冲刷函数fflush #include <stdio.h> #include <unistd.h> int main() {printf("I am a \nhandsome man!&q…

排序(1)——直接插入排序、希尔排序

目录 一、直接插入排序 1.简介 2.思路与代码 3.复杂度与稳定性分析 &#xff08;1&#xff09;时间复杂度 &#xff08;2&#xff09;空间复杂度 &#xff08;3&#xff09;稳定性 二、希尔排序 1.简介 2.思路与代码 &#xff08;1&#xff09;分组排序 &#xff08…

系统架构设计师教程(十七)通信系统架构设计理论与实践

通信系统架构设计理论与实践 17.1 通信系统概述17.2 通信系统网络架构17.2.1局域网网络架构17.2.2 广域网网络架构17.2.3 移动通信网网络架构17.2.4存储网络架构17.2.5 软件定义网络架构17.3 网络构建关键技术17.3.1 网络高可用设计17.3.2 IPv4与IPv6融合组网技术17.3.3 SDN技术…

09. Springboot集成sse服务端推流

目录 1、前言 2、什么是SSE 2.1、技术原理 2.2、SSE和WebSocket 2.2.1、SSE (Server-Sent Events) 2.2.2、WebSocket 2.2.3、选择 SSE 还是 WebSocket&#xff1f; 3、Springboot快速集成 3.1、添加依赖 3.2、创建SSE控制器 3.2.1、SSEmitter创建实例 3.2.2、SSEmi…

macOS跨进程通信: Unix Domain Socket 创建实例

macOS跨进程通信: Unix Domain Socket 创建实例 一&#xff1a; 简介 Socket 是 网络传输的抽象概念。 一般我们常用的有Tcp Socket和 UDP Scoket&#xff0c; 和类Unix 系统&#xff08;包括Mac&#xff09;独有的 Unix Domain Socket&#xff08;UDX&#xff09;。 Tcp So…

Scratch:启蒙少儿编程的图形化魔法

在当今这个数字化时代&#xff0c;编程已经成为了一项重要的基础技能。就像学习阅读和写作一样&#xff0c;掌握编程能够打开通往未来世界的大门。对于孩子们来说&#xff0c;Scratch作为一种图形化编程语言&#xff0c;不仅简单有趣&#xff0c;而且非常适合作为编程学习的入门…

科技助力“双碳”:墨水屏电子桌牌在绿色办公中的作用

随着科技的发展&#xff0c;人们对绿色环境可持续发展也越来越重视&#xff0c;所以&#xff0c;我国在几年前&#xff0c;就提出了“双碳”政策&#xff08;即碳达峰与碳中和的简称。2020年9月中国明确提出2030年“碳达峰”与2060年“碳中和”目标&#xff09;&#xff0c;而作…

单调栈第二天(还没写完)

503.下一个更大元素II 力扣题目链接(opens new window) 给定一个循环数组&#xff08;最后一个元素的下一个元素是数组的第一个元素&#xff09;&#xff0c;输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更…

卸载软件Geek Uninstaller,MySQl安装不成功

最近刷最右的时候&#xff0c;看到两个帖子都是MySQl安装过程总是出现问题。大概两年前我也遇到了这个问题&#xff0c;推荐一款软件。 是因为在安装的过程之中&#xff0c;出现了问题。然后你再进行安装的时候&#xff0c;没有完全将原来安装的软件卸载掉&#xff0c;导致有注…

垃圾填埋气体监测与告警一体化环保监测5G云网关

数字化时代数据采集和传输我认为变得非常重要。为了满足这一需求&#xff0c;我们推出了一款具备多种功能的数据采集器。这款产品不仅集成了8DI干湿节点、4DO继电器、6AI电流/电压型传感器&#xff0c;还支持与多个云平台进行上行对接。通过这些功能&#xff0c;用户可以轻松实…

深入浅出理解目标检测的非极大值抑制(NMS)

一、参考资料 物体检测中常用的几个概念迁移学习、IOU、NMS理解 目标定位和检测系列&#xff08;3&#xff09;&#xff1a;交并比&#xff08;IOU&#xff09;和非极大值抑制&#xff08;NMS&#xff09;的python实现 Pytorch&#xff1a;目标检测网络-非极大值抑制(NMS) …

机器学习整理

绪论 什么是机器学习&#xff1f; 机器学习研究能够从经验中自动提升自身性能的计算机算法。 机器学习经历了哪几个阶段&#xff1f; 推理期&#xff1a;赋予机器逻辑推理能力 知识期&#xff1a;使机器拥有知识 学习期&#xff1a;让机器自己学习 什么是有监督学习和无监…

CubeMX生成工程文件夹解释

使用CubeMXKeil的工程&#xff0c;物理文件夹结构如下&#xff1a; 文件夹、文件&#xff0c;众多&#xff0c;但我们平时使用到的&#xff0c;主要是两个入口文件&#xff0c;即以下的&#xff1a;1和2. 1、***.uvprojx 位置&#xff1a;工程目录\MDK-ART文件夹下。 Keil的工…

【C++】STL和vector容器

STL和vector容器 基本概念六大组件容器算法迭代器容器算法迭代器 vector容器基本概念vector构造函数赋值vector的容量和大小vector插入与删除vector存取数据函数原型 vector互换容器vector预留空间vector容器嵌套容器 基本概念 长久以来&#xff0c;软件届一直希望建立一种可重…