【Go 基础】channel
Go 基础
channel
什么是channel,为什么它可以做到线程安全
Go 的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存。 前者就是传统的加锁,后者就是 channel。也即,channel 的主要目的就是在多任务间传递数据的,本身就是安全的。
- channel 是 Go 中的一个核心类型,它可以看作是一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。
- channel 也可以理解为一个先进先出队列,通过管道进行通信;
- 发送一个数据到 channel 和从 channel 中接收一个数据都是原子性的;
channel的生命周期状态有哪些
channel 存在 3 种状态:
- nil:未初始化的状态,只进行了声明,或者手动赋值为 nil
- active:正常的 channel,可以进行读写;
- closed:已关闭,注意已关闭的 channel,它的值也不是 nil
针对不同状态的 channel,进行关闭,发送数据以及接收数据,会有不同的情况:
| 操作 | 一个零值 nil 通道 | 一个非零值但已关闭通道 | 一个非零值且未关闭通道 |
|---|---|---|---|
| 关闭 | 产生恐慌 | 产生恐慌 | 成功关闭 |
| 发送数据 | 永久阻塞 | 产生恐慌 | 阻塞或者成功发送 |
| 接收数据 | 永久阻塞 | 永不阻塞(会立即返回零值) | 阻塞或者成功接收 |
channel 的类型
channel 通常有以下三种类型:
- 同步 channel:不需要缓冲区,发送方会直接将数据交给接收方;
- 异步 channel:基于环形缓存的传统生产者消费者模型;
- chan struct{}:这是专门用于协程间通信的标准信号,因为 struct{} 不占用内存空间,所以用的比较多;
Goroutine 和 channel 的作用分别是什么
这里可以先简单说下,进程、线程以及协程之间的关系。
进程是内存资源和 CPU 调度的执行单元。为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程中存在多个线程,这多个线程还是共享同一片内存空间,但 CPU 调度的最小单元变成了线程。
而协程,可以看作是轻量级线程。但是,和线程不同的是,线程的切换是由操作系统控制的,而协程的切换是由用户控制的。
Go 中的 Goroutine 就是协程,可以实现并行,多个协程可以在多个处理器同时跑。而协程同一时刻只能在一个处理器上跑。多个 Goroutine 之间的通信就是通过 channel,而协程的通信是通过 yield 和 resume() 操作。
在 Golang 中 channel 是 goroutinues 之间进⾏通信的渠道。
可以把 channel 形象⽐喻为⼯⼚⾥的传送带,⼀头的⽣产者 goroutine 往传输带放东⻄,另⼀头的消费者 goroutinue 则从输送带取东⻄。channel 实际上是⼀个有类型的消息队列,遵循先进先出的特点。
goroutine 的使用
只需要在函数的调用前面加 go 关键字,就可以启动协程了:
func main() {for i:=1;i<5;i++ {go func(i int) {fmt.Println(i)}(i)}// 停歇5s,保证打印全部结束time.Sleep(5*time.Second)
}
上面的代码中,启动了 5 个 goroutine,再加上 main 函数的主 goroutine,一共 6 个 goroutine。由于 goroutine 类似于“守护线程”,是异步执行的。如果主 goroutine 不等待,程序可能就不会有打印输出了。
channel 的使用
-
channel 的操作符号
ch <- data 表示 data 被发送给 channel ch ;
data <- ch 表示从 channel ch 取⼀个值,然后赋给 data;
-
阻塞式 channel
channel 默认是没有缓冲区的,也即,通信是阻塞的。send 操作必须等到有消费者 accept 才算完成。
func main() {ch1 := make(chan int)go pump(ch1) // pump hangsfmt.Println(<-ch1) // prints only 1 }func pump(ch chan int) {for i := 1; ; i++ {ch <- i} }在函数 pump() ⾥的 channel 在接受到第⼀个元素后就被阻塞了,直到主 goroutinue 取⾛了数据。最终channel 阻塞在接受第⼆个元素,程序只打印 1。
没有缓冲的 channel 只能容纳⼀个元素,⽽带有缓冲 channel 则可以⾮阻塞容纳 N 个元素。发送数据到缓冲 channel 不会被阻塞,除⾮channel已满;同样的,从缓冲 channel 取数据也不会被阻塞,除⾮ channel 空了。
Go 中 channel 的实现
前文其实就一直有提到了:channel 是 Go 中 goroutines 之间的信息传递媒介,通过共享通信,来实现共享内存。

goroutine 通过使⽤ channel 传递数据,⼀个会向 Channel 中发送数据,另⼀个会从 Channel 中接收数据,它们两者能够独⽴运⾏并不存在直接关联,但是能通过 Channel 间接完成通信。
channel 的收发操作均遵循来先进先出的设计:
- 先从 channel 读取数据的 goroutine 会先接收到数据
- 先往 channel 发送数据的 goroutine 会得到先发送数据的权利
channel 在 runtime 中的具体实现
在 runtime.hchan 中定义了 channel:
type hchan struct {qcount uint // 当前队列⾥还剩余元素个数dataqsiz uint // 环形队列⻓度,即缓冲区的⼤⼩,即make(chan T,N)中的Nbuf unsafe.Pointer // 环形队列指针elemsize uint16 // 每个元素的⼤⼩closed uint32 // 标识当前通道是否处于关闭状态,创建通道后,该字段设置0,即打开通道;通道调⽤close将其设置为1,通道关闭elemtype *_type // 元素类型,⽤于数据传递过程中的赋值sendx uint // 环形缓冲区的状态字段,它只是缓冲区的当前索引-⽀持数组,它可以从中发送数据recvx uint // 环形缓冲区的状态字段,它只是缓冲区当前索引-⽀持数 组,它可以从中接受数据recvq waitq // 等待读消息的goroutine队列sendq waitq // 等待写消息的goroutine队列// 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 // 互斥锁,为每个读写操作锁定通道,因为发送和接受必须是互斥操作
}
type waitq struct {first *sudoglast *sudog
}
其中,hchan 结构体中有五个字段是构建底层的循环队列:
- qcount:channel 中剩余元素的个数
- dataqsiz:channel 中循环队列的长度
- buf:channel 的缓冲区数据指针
- sendx:channel 的发送操作处理到的位置
- recvx:channel 的接收操作处理到的位置
elemsize 和 elemtype 分别表示当前 channel 能够收发的元素类型和大小。
sendq 和 recvq 存储了当前 channel 由于缓冲区不足而阻塞的 goroutine 列表,这些等待队列使用双向链表 runtime.waitq 表示,链表中所有的元素都是 runtime.sudog 结构。
waitq 表示一个在等待队列中的 goroutine,该结构体存储了阻塞的相关信息以及两个分别指向前后 runtime.sudog 的指针。
创建 channel
runtime.makechan 和 runtime.makechan64 会根据传入的参数类型和缓冲区大小创建一个新的 channel 结构,其中后者用于处理缓冲区大小大于 2 的 32 次方的情况。
我们以 makechan 函数为例:
func makechan(t *chantype, size int) *hchan {elem := t.elem// compiler checks this but be safe.if elem.size >= 1<<16 {throw("makechan: invalid channel element type")}if hchanSize%maxAlign != 0 || elem.align > maxAlign {throw("makechan: bad alignment")}mem, overflow := math.MulUintptr(elem.size, uintptr(size))if overflow || mem > maxAlloc-hchanSize || size < 0 {panic(plainError("makechan: size out of range"))}// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.// buf points into the same allocation, elemtype is persistent.// SudoG's are referenced from their owning thread so they can't be collected.// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.var c *hchanswitch {case mem == 0:// Queue or element size is zero.c = (*hchan)(mallocgc(hchanSize, nil, true))// Race detector uses this location for synchronization.c.buf = c.raceaddr()case elem.ptrdata == 0:// Elements do not contain pointers.// Allocate hchan and buf in one call.c = (*hchan)(mallocgc(hchanSize+mem, nil, true))c.buf = add(unsafe.Pointer(c), hchanSize)default:// Elements contain pointers.c = new(hchan)c.buf = mallocgc(mem, elem, true)}c.elemsize = uint16(elem.size)c.elemtype = elemc.dataqsiz = uint(size)lockInit(&c.lock, lockRankHchan)if debugChan {print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")}return c
}
channel 中根据收发元素的类型和缓冲区的大小初始化 runtime.hchan 结构题和缓冲区:

arena 区域就是我们所谓的堆区,Go 动态分配的内存都是在这个区域,它把内存分割成 8KB 大小的页,一些页组合起来称为 mspan。
bitmap 区域标识 arena 区域哪些地址保存了对象,并且用 4bit 标志位表示对象是否包含指针、GC 标记信息。bitmap 中一个 byte 大小的内存对应 arena 区域中 4 个指针大小(指针大小为 8B)的内存,所以 bitmap 区域的大小是 512GB/(4*8B)=16GB。

此外,我们还可以看到 bitmap 的高地址部分指向 arena 区域的低地址部分,这里 bitmap 的地址是由高地址向低地址增长的。
spans 区域存放 mspan(是一些 arena 分割的页组合起来的内存管理基本单元)的指针,每个指针对应一页,所以 spans 区域的大小就是 512GB/8KB*8B=512MB。

除以 8KB 是计算 arena 区域的页数,而最后乘以 8 是计算 spans 区域所有指针的大小。创建 mspan 的时候,按页填充对应的 spans 区域,在回收 object 时,根据地址很容易就能找到它所属的 mspan。
相关文章:
【Go 基础】channel
Go 基础 channel 什么是channel,为什么它可以做到线程安全 Go 的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存。 前者就是传统的加锁,后者就是 channel。也即,channel 的主要目的就是在多任务间传递…...
windows10更新后system磁盘占用100%
windows10更新后system磁盘占用100% 现象: 解决办法: 打开服务禁用:Connected User Experiences and Telemetry 我现在已经把该服务禁用了,已经没有再出现不停写入的情况。 服务描述:“已连接的用户体验和遥测服务所…...
无人设备遥控器之防水性能篇
无人设备遥控器的防水性能是评估其耐用性和适应不同环境能力的重要指标。随着无人设备技术的不断发展,越来越多的遥控器在设计时融入了防水元素,以满足用户在不同天气条件下的使用需求。 一、防水等级与标准 无人设备遥控器的防水性能通常通过防水等级来…...
基于Matlab BP神经网络的非线性系统辨识与控制研究
随着现代工业和科学技术的不断发展,非线性系统的建模和控制成为了自动化领域中的重要研究课题。传统的系统辨识方法往往难以应对系统的复杂性和非线性特性,而人工神经网络(ANN)凭借其强大的逼近能力和自适应性,已广泛应…...
3D基因组工具(HiC可视化)trackc--bioinfomatics tools 35
01 3D genome data analysis guides 茶树三维基因组-文献精读19 https://trackc.readthedocs.io/en/latest/install.html #官网 https://github.com/seqyuan/trackc #官网https://trackc.readthedocs.io/en/latest/analysis_guide/index.html #HiC可视化案例 …...
【大模型微调】图片转pdf
有时候图片需要转成pdf https://www.bilibili.com/opus/982151156821131282 https://help.pdf24.org/ https://www.bilibili.com/video/BV163v2eyEWo/?vd_source=8318f88fcdf4948d2b21fae7c9cf3184 2024最新!小白如何安装破解版的 Acrobat https://www.32r.com/zt/dgyjzzrj/ …...
Linux-Ubuntu16.04摄像头 客户端抓取帧并保存为PNG
1.0:client.c抓取帧并保存为PNG #include <stdio.h> // 标准输入输出库 #include <stdlib.h> // 标准库,包含内存分配等函数 #include <string.h> // 字符串操作库 #include <linux/videodev2.h> // V4L2 视频设备…...
手机ip地址取决于什么?可以随便改吗
手机IP地址是指手机在连接到互联网时所获得的唯一网络地址,这个地址由一串数字组成,用于在网络中标识和定位设备。每个设备在连接到网络时都会被分配一个IP地址,它可以帮助数据包在网络中准确地找到目标设备。那么,手机IP地址究竟…...
计算机网络:TCP/IP协议的五大重要特性介绍
目录 一、逻辑编址 二、路由选择 三、名称解析 四、错误控制和流量控制 五、多应用支持 今天给大家聊聊TCP/IP协议中五大重要特性相关的知识,希望对大家深入了解该协议提供一些帮助! 一、逻辑编址 首先要了解什么是物理地址、逻辑地址。 ●...
Java与AWS S3的文件操作
从零开始:Java与AWS S3的文件操作 一、什么是 AWS S3?AWS S3 的特点AWS S3 的应用场景 二、Java整合S3方法使用 MinIO 客户端操作 S3使用 AWS SDK 操作 S3 (推荐使用) 三、总结 一、什么是 AWS S3? Amazon Simple Sto…...
详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法
详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法 这段代码中使用的命令行参数允许用户在运行 YOLOv5 模型时自定义多种行为和设置。以下是各个参数的详细说明和使用示例,以及如何在 PyCharm 中设置这些参数以确保正确运行带有参数的脚本。 命令行…...
Vue根据Div内容的高度给其Div设置style height
在 Vue.js 中,你可以使用 JavaScript 来动态地根据 div 的内容高度来设置其 style 的 height 属性。这通常是在组件挂载或更新时完成的,因为这时你已经有了实际的 DOM 元素可以操作。 以下是一个简单的例子,展示了如何实现这一点:…...
驱动篇的开端
准备 在做之后的动作前,因为win7及其以上的版本默认是不支持DbgPrint(大家暂时理解为内核版的printf)的打印,所以,为了方便我们的调试,我们先要修改一下注册表 创建一个reg文件然后运行 Windows Registr…...
OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)
前言 1、前面写过一篇 阿里云免费ssl证书申请与部署,大家可以去看下 一、openssl 安装说明 1、这部分就不再说了,我使用centos7.9,是自带 openssl的,window的话,要去下载安装 二、CA机构 CA机构,全称为…...
吾杯网络安全技能大赛WP(部分)
吾杯网络安全技能大赛WP(部分) MISC Sign 直接16进制解码即可 原神启动 将图片用StegSolve打开 找到了压缩包密码 将解出docx文件改为zip 找到了一张图片和zip 再把图片放到stegSlove里找到了img压缩包的密码 然后在document.xml里找到了text.zip压缩包密码 然后就出来fl…...
按vue组件实例类型实现非侵入式国际化多语言翻译
#vue3##国际化##本地化##international# web界面国际化,I18N(Internationalization,国际化),I11L(International,英特纳雄耐尔),L10N(Localization,本地化)&…...
Java入门:22.集合的特点,List,Set和Map集合的使用
1 什么是集合 本质就是容器的封装,可以存储多个元素 数组一旦创建,长度就不能再改变了。 数组一旦创建,存储内容的类型不能改变。 数组可以存储基本类型,也可以存储引用类型。 数组可以通过length获得容量的大小,但…...
重生之我在异世界学编程之C语言:深入指针篇(下)
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 目录 题集(1)指针笔试题1&a…...
理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发
parquet发音:美 [pɑrˈkeɪ] 镶木地板;拼花木地板 理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发 引言 在机器学习和大数据处理中,数据的存储和传输格式对于性能至关重要。两种广泛使用的格式是 Parquet 和 Arr…...
下载 M3U8 格式的视频
要下载 M3U8 格式的视频(通常是 HLS 视频流),可以尝试以下几种方法: 方法 1:使用下载工具(推荐) 1. IDM(Internet Download Manager): 安装 IDM 并启用浏…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...
网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
