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 int
	ch = make(chan int) // 无缓冲chan
	go func() {         // 创建一个协程,不断从chan中读取数据
		<-ch
	}()
	ch <- 1 // 写入数据,因为没有缓冲,如果没有上面那句,这句会阻塞

	var ch1 chan int
	ch1 = make(chan int, 1) // 有缓冲chan
	ch1 <- 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 := <- ch
  j, 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 []int
	data = make([]int, 10)
	for i := 0; i < 100; i++ {
		data[i] = i
		if 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)
	// 每两秒发送 one
	go func() {
		for {
			sayOne <- "one"
			time.Sleep(2 * time.Second)
		}
	}()
	// 每两秒发送 two
	go 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)
	// 每两秒发送 one
	go func() {
		for {
			sayOne <- "one"
			time.Sleep(2 * time.Second)
		}
	}()
	// 每两秒发送 two
	go func() {
		for {
			sayTwo <- "tow"
			time.Sleep(2 * time.Second)
		}
	}()
	// 设置超时
	//time.AfterFunc(10*time.Second, func() {
	//	os.Exit(0)
	//})
	tm := 0
	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
		default:
			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秒后关闭chan
	time.AfterFunc(5*time.Second, func() {
		close(ch)
	})
	// 每一秒读取一个元素
	for i := range ch {
		fmt.Println(i)
		time.Sleep(time.Second)
	}

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ssh异常报错:Did not receive identification string from

一、问题描述 某次外出在异地工作场所xshell炼乳远程服务器时&#xff0c;报错&#xff1a;Connection closed by foreign host. D&#xff0c;服务器查看secure日志或sshd服务状态会显示&#xff1a;id not receive identification string from client_ip; 二、分析处理 1&a…

综合案例 - 商品列表

文章目录 需求说明1.my-tag组件封装&#xff08;完成初始化&#xff09;2.may-tag封装&#xff08;控制显示隐藏&#xff09;3.my-tag组件封装&#xff08;v-model处理&#xff1a;信息修改&#xff09;4.my-table组件封装&#xff08;整个表格&#xff09;①数据不能写死&…

修复idea,eclipse ,clion控制台中文乱码

控制台乱码问题主要原因并不在编译器IDE身上&#xff0c;还主要是Windows的控制台默认编码问题。。。 Powershell&#xff0c;cmd等默认编码可能不是UTF-8&#xff0c;无需改动IDE的settings或者properties&#xff08;这治标不治本&#xff09;&#xff0c;直接让Windows系统…

编写nginx脚本,安装失败

这是我写的nginx脚本-&#xff08;正确的&#xff0c;已经修改过的&#xff09; 这是我在运行脚本是出现的问题 这是我在nginx官网上粘贴的内容&#xff0c;请注意我用红笔画的地方&#xff0c;与第一张我写的脚本图片作对比&#xff0c;会发现多出现两个转义符号\。第二幅图就…

Axolotl:一款极简的大模型微调(Finetune)开源框架

今天给大家分享一款工具&#xff0c;Axolotl[1] 是一个旨在简化各种AI模型的微调过程的工具&#xff0c;支持多种配置和架构。 特点&#xff1a; 可训练各种 Huggingface 模型&#xff0c;如 llama、pythia、falcon、mpt支持 fullfinetune、lora、qlora、relora 和 gptq使用简…

Linux GPIO模拟SPI接口介绍

一. 前言 上次通过GPIO模拟IIC的驱动一块0.98英寸的OLED后&#xff0c;想着下次用下GPIO模拟SPI接口试下。本文会介绍怎么用GPIO模拟SPI接口的方式为设备添加一块spi nor flash&#xff0c;flash模块如下图所示&#xff1a; 上图是一个spi nor模块&#xff0c;上面焊接了W25Q32…

acwing周赛140 b题

思路&#xff1a;我们按照从小到大的顺序将数组逆转好&#xff0c;然后枚举数组首项&#xff0c;分别让其1&#xff0c;-1&#xff0c;0&#xff0c;然后求出公差&#xff0c;从前往后遍历即可。 代码&#xff1a; int ans1(){//不动int cha (a[n] - a[1] 1) / (n - 1);int…

原创改进 | 融合蝠鲼觅食与联想学习的量子多目标灰狼优化算法(Matlab)

​前面的文章里作者介绍了多目标灰狼优化算法(Multi-Objective Grey Wolf Optimizer&#xff0c;MOGWO)&#xff0c;该算法是由Mirjalili等(灰狼算法的提出者)于2016年提出[1]&#xff0c;发表在中科院一区期刊《expert systems with applications》。 MOGWO保留了灰狼算法的种…

无际线复选框

效果演示 实现了一个网格布局&#xff0c;其中每个网格是一个复选框&#xff0c;可以选择是否显示。每个复选框都有一个漂浮的天花板&#xff0c;表示它是一个房间的天花板。每个房间的天花板都有一个不同的形状和颜色&#xff0c;分别对应不同的房间。整个页面的背景是一个由两…

小米13utltra激活冰箱

安装adb驱动https://www.jianshu.com/p/6c41d34d9202 直接下载使用 连接上小米手机,退出账号,关闭锁屏密码,打开usb调试,usb调试(安全设置) adb shell dpm set-device-owner com.catchingnow.icebox/.receiver.DPMReceiver如果显示Success&#xff0c;则您已成功激活冰箱。 …

【DeepLearning-8】MobileViT模块配置

完整代码&#xff1a; import torch import torch.nn as nn from einops import rearrange def conv_1x1_bn(inp, oup):return nn.Sequential(nn.Conv2d(inp, oup, 1, 1, 0, biasFalse),nn.BatchNorm2d(oup),nn.SiLU()) def conv_nxn_bn(inp, oup, kernal_size3, stride1):re…

PostgreSQL技术大讲堂 - 第43讲:流复制原理

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第43讲&#…

每日OJ题_算法_二分查找⑦_力扣153. 寻找旋转排序数组中的最小值

目录 力扣153. 寻找旋转排序数组中的最小值 解析代码 力扣153. 寻找旋转排序数组中的最小值 153. 寻找旋转排序数组中的最小值 - 力扣&#xff08;LeetCode&#xff09; 难度 中等 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后…

Java NIO初体验

概述 由于BIO&#xff08;同步阻塞IO&#xff09;对系统资源的浪费较大。Java1.4中引⼊了NIO框架&#xff0c;在java.nio包中提供了Channel、Selector、Buffer等抽象类&#xff0c;可以快速构建多路复⽤的IO程序&#xff0c;⽤于提供更接近操作系统底层的⾼性能数据操作⽅式。…

11. 双目视觉之立体视觉基础

目录 1. 深度恢复1.1 单目相机缺少深度信息1.2 如何恢复场景深度&#xff1f;1.3 深度恢复的思路 2. 对极几何约束2.1 直观感受2.2 数学上的描述 1. 深度恢复 1.1 单目相机缺少深度信息 之前学习过相机模型&#xff0c;最经典的就是小孔成像模型。我们知道相机通过小孔成像模…

零基础轻松学编程,中文编程开发工具下载及构件教程

零基础轻松学编程&#xff0c;中文编程开发工具下载及构件教程 一、前言 零基础学编程&#xff0c;中文编程工具下载&#xff0c;中文编程开发工具构件之选择件列表框构件教程&#xff0c; 想学编程可 编程交流.群.一起交流学习&#xff08;关注收藏或点击最下方官网卡片进入…

【c语言】详解操作符(上)

1. 操作符的分类 2. 原码、反码、补码 整数的2进制表示方法有三种&#xff0c;即原码、反码、补码 有符号整数的三种表示方法均有符号位和数值位两部分&#xff0c;2进制序列中&#xff0c;最高位的1位是被当做符号位其余都是数值位。 符号位都是用0表示“正”&#xff0c;用…

设计模式:简介及基本原则

简介 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问&#xff0c;设计模式于己于他人于系统都是多赢的&#xff0c;设计模式使代码编制真正工程化&#xff…

Kotlin快速入门5

Kotlin的继承与重写 kotlin的继承 Kotlin中所有类都继承自Any类&#xff0c;Any类是所有类的超类&#xff0c;对于没有超类型声明的类是默认超类&#xff08;Any 不是 java.lang.Object&#xff09;&#xff1a; class LearnKotlin // 默认继承自Any Any类默认提供三个函数…

Scikit-learn (sklearn)速通 -【莫凡Python学习笔记】

视频教程链接&#xff1a;【莫烦Python】Scikit-learn (sklearn) 优雅地学会机器学习 视频教程代码 scikit-learn官网 莫烦官网学习链接 本人matplotlib、numpy、pandas笔记 1 为什么学习 Scikit learn 也简称 sklearn, 是机器学习领域当中最知名的 python 模块之一. Sk…
最新文章