当前位置:首页 > 文章列表 > Golang > Go教程 > Go底层channel实现原理及示例详解

Go底层channel实现原理及示例详解

来源:脚本之家 2022-12-24 07:46:21 0浏览 收藏

怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Go底层channel实现原理及示例详解》,涉及到channel、底层原理,有需要的可以收藏一下

概念:

Go中的channel 是一个队列,遵循先进先出的原则,负责协程之间的通信(Go 语言提倡不要通过共享内存来通信,而要通过通信来实现内存共享,CSP(Communicating Sequential Process)并发模型,就是通过 goroutine 和 channel 来实现的)

使用场景:

停止信号监听

定时任务

生产方和消费方解耦

控制并发数

底层数据结构:

通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体

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

hchan结构体:

type hchan struct {
 closed   uint32   // channel是否关闭的标志
 elemtype *_type   // channel中的元素类型
 // channel分为无缓冲和有缓冲两种。
 // 对于有缓冲的channel存储数据,使用了 ring buffer(环形缓冲区) 来缓存写入的数据,本质是循环数组
 // 为啥是循环数组?普通数组不行吗,普通数组容量固定更适合指定的空间,弹出元素时,普通数组需要全部都前移
 // 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
 buf      unsafe.Pointer // 指向底层循环数组的指针(环形缓冲区)
 qcount   uint           // 循环数组中的元素数量
 dataqsiz uint           // 循环数组的长度
 elemsize uint16                 // 元素的大小
 sendx    uint           // 下一次写下标的位置
 recvx    uint           // 下一次读下标的位置
 // 尝试读取channel或向channel写入数据而被阻塞的goroutine
 recvq    waitq  // 读等待队列
 sendq    waitq  // 写等待队列
 lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}

等待队列:

双向链表,包含一个头结点和一个尾结点

每个节点是一个sudog结构体变量,记录哪个协程在等待,等待的是哪个channel,等待发送/接收的数据在哪里

type waitq struct {
   first *sudog
   last  *sudog
}
type sudog struct {
    g *g
    next *sudog
    prev *sudog
    elem unsafe.Pointer 
    c        *hchan 
    ...
}

操作:

创建

使用 make(chan T, cap) 来创建 channel,make 语法会在编译时,转换为 makechan64 和 makechan

func makechan64(t *chantype, size int64) *hchan {
    if int64(int(size)) != size {
        panic(plainError("makechan: size out of range"))
    }
    return makechan(t, int(size))
}

创建channel 有两种,一种是带缓冲的channel,一种是不带缓冲的channel

// 带缓冲
ch := make(chan int, 3)
// 不带缓冲
ch := make(chan int)

创建时会做一些检查:

  • 元素大小不能超过 64K
  • 元素的对齐大小不能超过 maxAlign 也就是 8 字节
  • 计算出来的内存是否超过限制

创建时的策略:

  • 如果是无缓冲的 channel,会直接给 hchan 分配内存
  • 如果是有缓冲的 channel,并且元素不包含指针,那么会为 hchan 和底层数组分配一段连续的地址
  • 如果是有缓冲的 channel,并且元素包含指针,那么会为 hchan 和底层数组分别分配地址

发送

发送操作,编译时转换为runtime.chansend函数

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool 

阻塞式:

调用chansend函数,并且block=true

ch 
<p>非阻塞式:</p>
<p>调用chansend函数,并且block=false</p>
<pre class="brush:go;">select {
    case ch 
<p>向 channel 中发送数据时大概分为两大块:检查和数据发送,数据发送流程如下:</p>
<p>如果 channel 的读等待队列存在接收者goroutine</p>
  • 将数据直接发送给第一个等待的 goroutine, 唤醒接收的 goroutine

如果 channel 的读等待队列不存在接收者goroutine

  • 如果循环数组buf未满,那么将会把数据发送到循环数组buf的队尾
  • 如果循环数组buf已满,这个时候就会走阻塞发送的流程,将当前 goroutine 加入写等待队列,并挂起等待唤醒

接收

发送操作,编译时转换为runtime.chanrecv函数

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) 

阻塞式:

调用chanrecv函数,并且block=true

<ch v :="<ch" ok for i ch fmt.println><p>非阻塞式:</p>
<p>调用chanrecv函数,并且block=false</p>
<pre class="brush:go;">select {
    case 
<p>向 channel 中接收数据时大概分为两大块,检查和数据发送,而数据接收流程如下:</p>
<p>如果 channel 的写等待队列存在发送者goroutine</p>
  • 如果是无缓冲 channel,直接从第一个发送者goroutine那里把数据拷贝给接收变量,唤醒发送的 goroutine
  • 如果是有缓冲 channel(已满),将循环数组buf的队首元素拷贝给接收变量,将第一个发送者goroutine的数据拷贝到 buf循环数组队尾,唤醒发送的 goroutine

如果 channel 的写等待队列不存在发送者goroutine

  • 如果循环数组buf非空,将循环数组buf的队首元素拷贝给接收变量
  • 如果循环数组buf为空,这个时候就会走阻塞接收的流程,将当前 goroutine 加入读等待队列,并挂起等待唤醒

关闭

关闭操作,调用close函数,编译时转换为runtime.closechan函数

close(ch)
func closechan(c *hchan) 

案例分析:

package main
import (
    "fmt"
    "time"
    "unsafe"
)
func main() {
  // ch是长度为4的带缓冲的channel
  // 初始hchan结构体重的buf为空,sendx和recvx均为0
    ch := make(chan string, 4)
    fmt.Println(ch, unsafe.Sizeof(ch))
    go sendTask(ch)
    go receiveTask(ch)
    time.Sleep(1 * time.Second)
}
// G1是发送者
// 当G1向ch里发送数据时,首先会对buf加锁,然后将task存储的数据copy到buf中,然后sendx++,然后释放对buf的锁
func sendTask(ch chan string) {
    taskList := []string{"this", "is", "a", "demo"}
    for _, task := range taskList {
        ch 
<p>总结hchan结构体的主要组成部分有四个:</p>
  • 用来保存goroutine之间传递数据的循环数组:buf
  • 用来记录此循环数组当前发送或接收数据的下标值:sendx和recvx
  • 用于保存向该chan发送和从该chan接收数据被阻塞的goroutine队列: sendq 和 recvq
  • 保证channel写入和读取数据时线程安全的锁:lock

今天带大家了解了channel、底层原理的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~

版本声明
本文转载于:脚本之家 如有侵犯,请联系study_golang@163.com删除
浅谈Golang内存逃逸浅谈Golang内存逃逸
上一篇
浅谈Golang内存逃逸
Golang交叉编译(跨平台编译)的使用
下一篇
Golang交叉编译(跨平台编译)的使用
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    3162次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    3375次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    3403次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    4506次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    3784次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码