go的通信Channel

go的通道channel是用于协程之间数据通信的一种方式

一、channel的结构

go源码:GitHub - golang/go: The Go programming language

src/runtime/chan.go

type hchan struct {qcount   uint           // total data in the queue 队列中当前元素计数,满了就=dataqsizdataqsiz uint           // size of the circular queue 环形队列大小(缓存大小)buf      unsafe.Pointer // points to an array of dataqsiz elements 指向任意类型的指针elemsize uint16  //元素大小closed   uint32  //是否关闭,0-未关闭,1-已关闭timer    *timer // timer feeding this chan //定时器elemtype *_type // element type //元素类型sendx    uint   // send index //发送索引recvx    uint   // receive index //结束索引recvq    waitq  // list of recv waiters //接收等待队列(<-ch)sendq    waitq  // list of send waiters //发送等待队列(ch<-)// lock protects all fields in hchan, as well as several// fields in sudogs blocked on this channel.//// Do not change another G's status while holding this lock// (in particular, do not ready a G), as this can deadlock// with stack shrinking.lock mutex //锁,保护hchan中的所有字段
}type waitq struct { //等待队列,sudog双向链表结构first *sudog //(伪g)表示等待列表中的g,例如在一个通道上用于发送/接收的glast  *sudog //用acquireSudog分配,releaseSudog释放

从结构体上可以记住channel的一些特点,比如说

(1)lock 锁:操作channel是互斥的。先获取锁,操作channel,释放锁

(2)elemtype类型:创建的时候必须指定类型(大小可指定) ch := make(chan int,10)

(3)waitq队列:FIFO先进先出队列,即通道,能通过任意类型(unsafe.Pointer)的数据,(sudog)双向链表的g

(4)dataqsiz通道容量:有值=有缓冲通道,没值=无缓冲通道

(5)qcount通道元素计数:当前通道内的元素个数总数

(6)接受和发送:通信,有人发还要有人收,意味必须2个g及以上的成员一起工作

(7)timer定时器:定时可对channel做特殊操作

(8)closed关闭:写(发送)已关闭通道会panic,读(接收)已关闭通道立刻返回:true,false

二、channel创建

创建:make(chan类型 元素类型,缓冲容量大小),var chan类型 元素类型

func Test_2(t *testing.T) {ch1 := make(chan int)       //双向ch11 := make(chan int, 10)  //双向,带缓冲容量10ch2 := make(chan<- int)     //只写ch22 := make(chan int, 10)  //只写,带缓冲容量10ch3 := make(<-chan float64) //只读ch33 := make(chan int, 10)  //只读,带缓冲容量10//go1.17_spec.html//chan T          // can be used to send and receive values of type T//chan<- float64  // can only be used to send float64s//<-chan int      // can only be used to receive intsvar ch4 chan int//通道是引用类型,通道类型的空值是nil。g.Dump(ch1, ch11, ch2, ch22, ch3, ch33, ch4)//打印//<chan int>//<chan int>//<chan<- int>//<chan int>//<<-chan float64>//<chan int>//<chan int>
}

(1)channel通道类型:

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" )=(双向|单向发送(写)|单向接收(读))

只写:操作读会报错。

使用场景,上下文:src/context/context.go的Context,

type Context interface {
...// a Done channel for cancellation.Done() <-chan struct{}
...}

只读:操作写会报错

使用场景,上下文:src/os/signal/signal.go的handlers,

var handlers struct {sync.Mutex// Map a channel to the signals that should be sent to it.m map[chan<- os.Signal]*handler...
}type stopping struct {c chan<- os.Signalh *handler
}

(2)channel数据类型:

任意,如:int,float64,bool,map...

(3)缓冲容量

第二个参数给定数量,如10

无缓冲通道:ch := make(chan int);

有缓冲通道:ch := make(chan int,10)

三、向channel写数据

channel的发送要注意区分【有缓冲容量】和【无缓冲容量】

1.有人接收:正常发送

func Test_send1(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//主goroutine和goroutine读写互斥,相互竞争锁。直到通道关闭主程结束
}

2.没人接收:

看通道容量情况:无缓存通道,容量0,一个都发不出,直接阻塞

注意:select是非阻塞发送,会直接返回

func Test_send2(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//写入ch 元素: 1//无缓存通道:没人接收,尝试发送1时,g被阻塞
}

思考为什么读要先于发?尝试从源码角度分析,因为无缓存channel的接收方会从发送方栈拷贝数据后,发送方才会被放回调度队列种,等待重新调度,如果一直没有读,发就一直卡住,无法被唤醒

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
...
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
...
}//1.c通道
//2.发送方sg发送的值被放入通道中,发送方被唤醒,继续它的快乐之路
//3.接收方接收到的值(当前G)为写入ep
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {//无缓冲读if c.dataqsiz == 0 {...if ep != nil {// copy data from sender 接收是直接从发送的栈进行拷贝recvDirect(c.elemtype, sg, ep)}} else {//有缓冲读// 从缓存队列拷贝qp := chanbuf(c, c.recvx)...}gp.param = unsafe.Pointer(sg)...//唤醒g准备执行goready(gp, skip+1)
}func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {// dst is on our stack or the heap, src is on another stack.// The channel is locked, so src will not move during this// operation.src := sg.elemtypeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)//从"from"拷贝n个字节到"to",from是src发送方,to就是dst接受方memmove(dst, src, t.Size_)
}
func memmove(to, from unsafe.Pointer, n uintptr)

有缓存通道,容量5,能先发5个,第6个阻塞

func Test_send4(t *testing.T) {ch := make(chan int, 5) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 10; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//写入ch 元素: 1//写入ch 元素: 2//写入ch 元素: 3//写入ch 元素: 4//写入ch 元素: 5//写入ch 元素: 6//无缓存通道:没人接收,前5个元素成功发送,尝试发送6时,g才被阻塞
}

3.发送到已关闭的通道,会panic

func Test_send3(t *testing.T) {ch := make(chan int) //双向close(ch)            //关闭通道ch <- 1//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//panic: send on closed channel
}

发送小结:

(1)如果channel为nil,如果非阻塞式发送(select send),直接返回false,否则阻塞

(2)如果channel已关闭,直接panic

(3)如果recevq等待队列有接收方,直接拷贝数据给接收方,并唤醒接收方的g

(4)如果channel缓冲区未满,发到缓冲区,否则阻塞,”保护发送现场“等待被唤醒

四、向channel读数据

1.有人发送:通道有值,遍历通道,如果通道未关闭,读完元素后,会报死锁的错误

func Test_read1(t *testing.T) {ch := make(chan int, 5) //双向for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch)for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}//写入ch 元素: 1//写入ch 元素: 2//写入ch 元素: 3//写入ch 元素: 4//写入ch 元素: 5//1 true//2 true//3 true//4 true//5 true//fatal error: all goroutines are asleep - deadlock!
}

2.没人发送:

(1)make或var初始化之后就读取,不管有无容量,都会一直阻塞,等待写入本次实验go版本(go1.20.5 )

注意:如果channel有值,并且一直没关闭,一直for循环读,读完之后会报死锁的错误。


func Test_read2(t *testing.T) {ch := make(chan int) //双向for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}
func Test_read3(t *testing.T) {ch := make(chan int, 5) //双向for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}
func Test_read4(t *testing.T) {var ch chan intfor i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}
}

3.读取已关闭的通道,会不会panic?

答案是:不会panic,不影响

已关闭通道,如果有值,返回:值,true

已关闭通道,如果没值,返回:0,false

func Test_read5(t *testing.T) {ch := make(chan int) //双向close(ch)for i := range ch {fmt.Println("读取ch 结果:", i)}//0 false//0 false//0 false//0 false//0 false//0 false//0 false//0 false
}func Test_read6(t *testing.T) {ch := make(chan int, 5) //双向for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch)for i := 1; i < 9; i++ {v, ok := <-chfmt.Println(v, ok)}//写入ch 元素: 1//写入ch 元素: 2//写入ch 元素: 3//写入ch 元素: 4//写入ch 元素: 5//1 true//2 true//3 true//4 true//5 true//0 false//0 false//0 false
}

4.for Range

for ... range阻塞式读取channel的值,直到channel被关闭

func Test_read(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//主goroutine和goroutine读写互斥,相互竞争锁。直到通道关闭主程结束
}

若close(ch)注释掉,读完数据之后,还继续读,会报死锁的错误。

func Test_read(t *testing.T) {ch := make(chan int) //双向//开启goroutine将1~5的数发送到ch中go func() {for i := 1; i <= 5; i++ {fmt.Println("写入ch 元素:", i)ch <- i}//close(ch) //写完,关闭通道}()//在主goroutine中从ch中接收值打印for i := range ch {fmt.Println("读取ch 结果:", i)}//写入ch 元素: 1//写入ch 元素: 2//读取ch 结果: 1//读取ch 结果: 2//写入ch 元素: 3//写入ch 元素: 4//读取ch 结果: 3//读取ch 结果: 4//写入ch 元素: 5//读取ch 结果: 5//fatal error: all goroutines are asleep - deadlock!
}

读取小结:

(1)如果channel为nil,如果非阻塞式接收(select receive),直接返回(false,false),否则阻塞

(2)如果channel计时器不为nil,检查计时,做超时处理

(3)如果channel上有可以接收的数据(empty函数),且是阻塞读(block=false),在channel未关闭时,返回(false,false),如果未关闭,再次检查empty函数,没可接收数据,返回(true,false)

(4)当channel已关闭,如果是无缓冲,返回(0,false),如果有缓存,执行recv()方法

        从sendq队列或缓冲区中拷贝数据

(5)当channel未关闭,有缓冲,读缓冲数据(如果非阻塞式接收(select receive),直接返回(false,false))

(6)当缓冲数据读完了,”保护接收现场“,等待被唤醒

五、channel死锁问题

1.同一个goroutine上执行

(1).未初始化的channel,读死锁,写死锁

func Test_deadlock1(t *testing.T) {// 未初始化的channel,直接写死锁var ch chan intch <- 1
}func Test_deadlock2(t *testing.T) {// 未初始化的channel,直接写死锁var ch chan int<-ch
}

(2).已初始化的channel

(2.1)无缓冲,直接读死锁,写死锁

func Test_deadlock3(t *testing.T) {// 初始化无缓冲的channel,直接写死锁ch := make(chan int)ch <- 1
}func Test_deadlock4(t *testing.T) {// 初始化无缓冲的channel,直接写死锁ch := make(chan int)val, ok := <-chfmt.Println(val, ok)
}

(2.2)有缓冲

先写后读(读完之后,死锁)

func Test_deadlock5(t *testing.T) {// 未初始化的channel,直接写死锁ch := make(chan int, 5)ch <- 1ch <- 2ch <- 3for v := range ch {fmt.Println(v)}//1//2//3//fatal error: all goroutines are asleep - deadlock!
}

先读后写(死锁)

func Test_deadlock6(t *testing.T) {// 未初始化的channel,直接写死锁ch := make(chan int, 5)for v := range ch {fmt.Println(v)}ch <- 1ch <- 2ch <- 3//fatal error: all goroutines are asleep - deadlock!
}

所以,通信只是发生在1端,容易造成死锁

2.不同的goroutine

五、关闭通道

关闭已关闭的通道,会panic

六、

六、总结

1.通道创建类型有3种:双向,只读,只写

2.通道有容量可设:无缓冲通道和有缓冲通道

3.通道读写互斥

4.已关闭通道,写panic,读有值,再关闭panic

5.通道通常需要2个g一起工作

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

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

相关文章

专题二_滑动窗口(2)

目录 1658. 将 x 减到 0 的最小操作数 解析 题解 904. 水果成篮 解析 题解 1658. 将 x 减到 0 的最小操作数 1658. 将 x 减到 0 的最小操作数 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:int minOperations(vector<int>& num…

MPDataDoc类介绍

MPDataDoc类介绍 使用mp数据库新接口mp_api.client.MPRester获取数据&#xff0c;例子如下&#xff1a; from mp_api.client import MPResterwith MPRester(API_KEY) as mpr:docs mpr.summary.search(material_ids["mp-1176451", "mp-561113"])以上代码返…

Java抽象类详解:定义、特性与实例化限制(day12)

抽象类 总结一下今天老师上课的内容&#xff0c;前面几节课听得是有点懵&#xff0c;在讲到内存问题&#xff0c;也就是代码在栈、堆、以及方法区是怎么执行的&#xff0c;听得不是很懂&#xff0c;今天讲到抽象类以及重写的机制&#xff0c;似乎开始慢慢懂得了java的底层原理…

Linux应用实战之网络服务器(三)CSS介绍

0、前言 准备做一个Linux网络服务器应用实战&#xff0c;通过网页和运行在Linux下的服务器程序通信&#xff0c;这是第三篇&#xff0c;介绍一下CSS&#xff0c;优化上一篇文章中制作的HTML页面。 1、CSS常用语法 CSS&#xff08;层叠样式表&#xff09;是用于描述HTML或XML…

FPGA 图像边缘检测(Canny算子)

1 顶层代码 timescale 1ns / 1ps //边缘检测二阶微分算子&#xff1a;canny算子module image_canny_edge_detect (input clk,input reset, //复位高电平有效input [10:0] img_width,input [ 9:0] img_height,input [ 7:0] low_threshold,input [ 7:0] high_threshold,input va…

【案例·增】一条insert语句批量插入多条记录

问题描述&#xff1a; 往MySQL中的数据库表中批量插入多条记录&#xff0c;可以使用 SQL 中的 ((), ()…)来处理 案例&#xff1a; INSERT INTO items(name,city,price,number,picture) VALUES(耐克运动鞋,广州,500,1000,003.jpg),(耐克运动鞋2,广州2,500,1000,002.jpg);规则…

基于java+springboot+vue实现的宠物领养救助平台(文末源码+Lw+ppt)23-363

摘 要 宠物领养救助平台采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户、…

【Redis】redis主从复制

概述 常见的Redis高可用的方案包括持久化、主从复制&#xff08;及读写分离&#xff09;、哨兵和集群。其中持久化侧重解决的是Redis数据的单机备份问题&#xff08;从内存到硬盘的备份&#xff09;&#xff1b;而主从复制则侧重解决数据的多机热备。此外&#xff0c;主从复制…

提高三维模型数据的立体裁剪技术

提高三维模型数据的立体裁剪技术 立体裁剪是三维模型处理中的重要步骤&#xff0c;可以用于去除模型中不需要的部分&#xff0c;提高模型的质量和准确性。本文将介绍几种常见的立体裁剪技术&#xff0c;包括边界裁剪、体素裁剪和几何裁剪&#xff0c;并分析它们的优缺点和适用场…

新朋友+1!拓数派 PieCloudDB Database 与 OpenCloudOS、TencentOS Server 完成产品兼容互认证

近日&#xff0c;拓数派旗下产品云原生虚拟数仓 PieCloudDB Database 与开源操作系统 OpenCloudOS 以及腾讯云旗下操作系统 TencentOS Server 完成了产品兼容性互认证。测试期间&#xff0c;双方产品运行稳定&#xff0c;兼容性良好&#xff0c;功能正常。 随着“数据要素x”三…

交互式RDP服务启停及修改端口的bat脚本

1、执行效果 2、脚本代码 echo off chcp 65001REM 检查是否有管理员权限 net session >nul 2>&1 if %errorlevel% neq 0 (echo 请右键【以管理员身份运行】此脚本。pauseexit /b )REM 提示是否开启或关闭RDP服务 set /p enable_disable是否开启或关闭RDP远程桌面服务…

生成的短链接/二维码,如何更改跳转网址?C1N 短网址一键解决

在当今的营销推广领域&#xff0c;短链接的运用已不可或缺。它能直接将网页、产品或服务呈现在潜在客户或用户面前&#xff0c;提升知名度与曝光率。 然而&#xff0c;使用短链接时也会遭遇一些问题&#xff0c;最常见的便是推广链接已发出&#xff0c;却发现有误或需修改&…

CC工具箱使用指南:【经度转3度带和6度带】

一、简介 在规划工作中&#xff0c;经常会遇到不清楚规划用地所在的3度带或6度带带号的情况。 其实只要知道所在地的经度即可计算出带号&#xff0c;具体计算方法百度可知&#xff1a; 三度带和六度带1.高斯投影6度带&#xff1a;自0子午线起每隔经差6自西向东分带&#xff…

神策数据参与制定首份 SDK 网络安全国家标准

国家市场监督管理总局、国家标准化管理委员会发布中华人民共和国国家标准公告&#xff08;2023 年第 13 号&#xff09;&#xff0c;全国信息安全标准化技术委员会归口的 3 项国家标准正式发布。其中&#xff0c;首份 SDK 国家标准《信息安全技术 移动互联网应用程序&#xff0…

2014年认证杯SPSSPRO杯数学建模B题(第二阶段)位图的处理算法全过程文档及程序

2014年认证杯SPSSPRO杯数学建模 B题 位图的处理算法 原题再现&#xff1a; 图形&#xff08;或图像&#xff09;在计算机里主要有两种存储和表示方法。矢量图是使用点、直线或多边形等基于数学方程的几何对象来描述图形&#xff0c;位图则使用像素来描述图像。一般来说&#…

某某消消乐增加步数漏洞分析

一、漏洞简介 1&#xff09; 漏洞所属游戏名及基本介绍&#xff1a;某某消消乐&#xff0c;三消游戏&#xff0c;类似爱消除。 2&#xff09; 漏洞对应游戏版本及平台&#xff1a;某某消消乐Android 1.22.22。 3&#xff09; 漏洞功能&#xff1a;增加游戏步数。 4&#xf…

【MATLAB源码-第22期】基于matlab的手动实现的(未调用内置函数)CRC循环码编码译码仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 循环码是线性分组码的一种&#xff0c;所以它具有线性分组码的一般特性&#xff0c;此外还具有循环性。循环码的编码和解码设备都不太复杂&#xff0c;且检(纠)错能力强。它不但可以检测随机的错误&#xff0c;还可以检错突发…

RPA使用Native Messaging协议实现浏览器自动化

RPA 即机器人流程自动化&#xff0c;是一种利用软件机器人或人工智能来自动化业务流程中规则性、重复性任务的技术。RPA 技术可以模拟和执行人类在计算机上的交互操作&#xff0c;从而实现自动化处理数据、处理交易、触发通知等任务。帮助企业或个人实现业务流程的自动化和优化…

【绝对有用】“利用ChatGPT赋能学术写作:技巧、方法与创新策略“

为了帮助读者利用ChatGPT进行学术写作&#xff0c;我以一种引人入胜的信息图表形式&#xff0c;概括了以下步骤和技巧&#xff1a; 使用英文提问&#xff1a;用英文提出问题&#xff0c;可以获得更准确的回答。生成大纲&#xff1a;通过创建文章大纲&#xff0c;为写作提供清晰…

leetcode热题100.柱状图中最大的矩形

Problem: 84. 柱状图中最大的矩形 文章目录 题目思路复杂度Code 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;hei…