当前位置: 首页 > news >正文

go channel 通道

一、底层实现

1、数据结构

type hchan struct {qcount   uint           // total data in the queuedataqsiz uint           // size of the circular queuebuf      unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed   uint32timer    *timer // timer feeding this chanelemtype *_type // element typesendx    uint   // send indexrecvx    uint   // receive indexrecvq    waitq  // list of recv waiterssendq    waitq  // list of send waiters// 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
}

        channel 用于 goroutine 之间通信和同步。主要由一个环形缓冲区(对于带缓冲的 channel)和两个指针(读指针和写指针)组成。每个 channel 还有一个锁(通常是自旋锁)来保证并发安全。

        主要结构:环形缓冲区+读写指针+读写等待队列+锁

2、发送与接收

        发送与接收是相对于协程而言的。

  • 发送操作(chan <- value)会检查 channel 是否已满:
    • 如果是无缓冲的 channel,发送会阻塞直到有接收操作。
    • 如果是有缓冲的 channel,发送会阻塞直到有空间可用。
  • 接收操作(value := <-chan)会检查 channel 是否为空:
    • 如果是无缓冲的 channel,接收会阻塞直到有发送操作。
    • 如果是有缓冲的 channel,接收会阻塞直到有数据可读

3、发送队列(sendq)和接收队列(recvq)

  1. recvq 队列:当一个 goroutine 执行接收操作时,Go 调度器会检查 channel 的状态,接收的 goroutine 会被挂起,并加入到 recvq 队列。

  2. sendq 队列:当一个 goroutine 执行发送被阻塞时,发送的 goroutine 会被挂起,并加入到 sendq 队列。

        发送和接收队列是FIFO队列,阻塞线程按先进先出顺序被调度,活跃线程优先于阻塞队列中的线程被调度。

4、调度

        调度器会负责管理协程的状态,协程被channel阻塞时进入等待队列,此时调度器可以将其他可运行的 goroutine 调度到 CPU 上。

二、内存管理

1、内存分配

        创建channel时分配内存,channel 内存的分配是通过内存分配器来完成的,它会根据需要为 channel 结构体和缓冲区(如果有)分配内存。

  • channel 的结构channel 是一个指向 chan 类型的结构体,这个结构体包含了 channel 的基本信息(例如缓冲区大小、读写指针等)。
  • 缓冲区(如果是缓冲 channel:如果 channel 是缓冲的(即使用 make(chan Type, size) 创建的 channel),Go 还需要为 channel 分配一个固定大小的缓冲区,以便存储数据。缓冲区的大小是 channel 类型的元素大小乘以缓冲区的长度。

2、内存回收

  当一个 channel 被销毁或不再有任何引用时,它占用的内存会被垃圾回收器回收。

  • channel 是引用类型,它本身是一个指针,指向一个底层的数据结构。这个底层结构体包含了与 channel 操作相关的数据,如缓冲区、队列、读写指针等
  • 由于 channel 的底层数据结构需要在堆上进行管理,因此即使它在栈上有一个指针,实际的数据存储通常是在堆上,特别是当它的生命周期超过函数作用域时。
  • 通过 逃逸分析,Go 运行时决定是否将 channel 分配到栈上或堆上。如果 channel 在函数外部被使用(例如通过返回值或传递给其他协程),它会被分配到堆上。
  • 如果 channel 仅在一个函数内部,并且没有被返回或传递出去(即它的生命周期完全在栈帧内),Go 运行时可能会将它分配到栈上。这个优化是由 Go 运行时的逃逸分析(escape analysis)决定的。如果 channel 的引用没有逃逸出函数,它可能会分配在栈上;否则,它将分配在堆上。

3、回收时机

  • channel 不再被引用时:如果 channel 变量超出了作用域,或者所有引用该 channel 的变量都被置为 nil 或销毁,垃圾回收器会将该 channel 标记为可回收对象。下一次 GC 执行时,它会回收这个 channel 占用的内存。
  • channel 的缓冲区:如果 channel 是一个带缓冲的 channelmake(chan T, N)),那么在回收 channel 结构本身时,缓冲区也会被回收。

4、内存分配和垃圾回收的优化

        Go 的垃圾回收器会尽可能地减少对内存的管理开销,但当涉及到大量的 channel 操作时,频繁的内存分配和垃圾回收可能会对性能造成影响。为了减少这种影响,可以考虑以下优化:

  • 复用 channel:如果可能的话,复用已经创建的 channel,而不是每次都重新创建。
  • 避免大缓冲区的 channel:如果 channel 的缓冲区过大,可能会占用大量内存,造成内存压力。使用适当大小的缓冲区可以减少内存消耗。
  • 及时关闭 channel:在不再需要 channel 时,应该尽早关闭它,或者确保 channel 变量没有持续的引用。

   channel 的关闭并不会直接触发垃圾回收,关闭 channel 只是告诉协程可以停止从该 channel 接收数据。在使用带缓冲区的 channel 时,关闭 channel 还意味着缓冲区中未处理的数据将无法再被写入。虽然关闭 channel 本身不影响垃圾回收的触发,但是关闭 channel 可以帮助协程更快地退出,从而可能减少内存泄漏的风险。如果一个 channel 被关闭且没有任何活跃的协程在使用它,那么这个 channel 很可能会更快地被垃圾回收器回收。


http://www.mrgr.cn/news/66702.html

相关文章:

  • Python中的PDF处理工具:PyPDF2和ReportLab使用指南
  • 如何在 Linux 服务器上安装 Git
  • 【Postman深入测试接口的详细指南】保姆级
  • C++设计模式结构型模式———组合模式
  • Web Broker(Web服务应用程序)入门教程(1)
  • 从无到有:模拟 STL 栈和队列的抽象构建艺术
  • 关于Catkin工作空间的两种方式
  • SpringBoot驱动的健身中心管理解决方案
  • Android AndroidManifest 文件内标签及属性
  • 虚假信息成为美国大选的首要安全问题
  • 安装acondana3, Conda command not found
  • 【Rust实现命令模式】
  • 【JAVA】Java基础—基础语法:运算符(算数、关系、逻辑运算符)
  • C++面经(一)
  • 【Ajax】跨域
  • AIDD - 分子药物发现的计算方法现状总结
  • 基于springboot+vue实现的旅行社网站系统
  • 辐射发射测试新境界:深入解析TS-RadiMation套件多种操作方法(一)
  • ubuntu 22.04 server 安装 mysql 5.7.40 更改 datadir 目录 LTS
  • 表单同时提交多条记录的技术实现
  • awk工具使用
  • 【Python编程实例】-深入理解Python线程安全
  • 【2023工业图像异常检测代码复现】DDAD: 基于条件去噪扩散模型的异常检测方法
  • [MySQL]DCL语句
  • APP开发者如何选择合适的聚合平台?
  • 论文写作总结