【后端面试总结】golang channel深入理解

在Go语言中,Channel是一种用于在goroutine之间进行通信和同步的重要机制。它提供了一种安全、类型安全的方式来传递数据,使得并发编程变得更加直观和简单。本文将详细介绍Golang中Channel的基本概念、创建与关闭、发送与接收操作,以及相关的使用场景和注意事项。另外,Channel本身也是Golang一个很核心的设计理念的良好体现,即:

Do not communicate by sharing memory; instead, share memory by communicating.

( 来源:Share Memory By Communicating - The Go Programming Language)

Golang的Channel基本介绍

Channel的基本概念

Channel是Go语言中的一种特殊类型,它像一个队列一样,遵循先进先出(FIFO)的原则,确保数据的顺序性。每个Channel都有一个指定的类型,只能传递相同类型的数据。Channel是并发安全的,允许多个goroutine同时读写,而不会引发数据竞争。

Channel的主要作用是实现goroutine之间的通信和同步。通过Channel,一个goroutine可以发送数据到另一个goroutine,从而实现数据的交换和共享。

Channel的创建与关闭

在Go中,可以使用内置的make函数来创建一个Channel。创建Channel时需要指定其传递的数据类型,并可以选择性地指定缓冲区大小。

// 创建一个无缓冲的Channel
ch1 := make(chan int) 
// 创建一个容量为10的缓冲Channel
ch2 := make(chan int, 10)

当不再需要向Channel发送数据,并且已经接收完所有数据时,应该关闭Channel。关闭Channel使用close函数。关闭后的Channel不能再发送数据,但可以继续接收已发送的数据,直到Channel为空。

close(ch1)

需要注意的是,关闭一个已经关闭的Channel会引发panic。

Channel的发送与接收操作

Channel的发送和接收操作使用操作符。发送数据到Channel使用ch 语法,从Channel接收数据使用value := 语法。

默认情况下,Channel的发送和接收操作都是阻塞的。即,在发送数据到Channel时,如果接收方没有准备好接收,发送方会被阻塞;同样,在从Channel接收数据时,如果发送方没有发送数据,接收方也会被阻塞。这种阻塞行为可以用于实现同步和协调。

如果需要实现非阻塞的发送和接收操作,可以使用select语句结合default子句。

Channel的使用场景

Channel在并发编程中有广泛的应用场景,包括但不限于:

  1. 消息传递
  2. Channel可以用于在不同的goroutine之间传递数据,实现基本的数据传输。
  3. 任务分发
  4. 可以将任务分发到多个goroutine中并行处理,每个goroutine处理完成后将结果发送回主goroutine或另一个处理结果的goroutine。
  5. 事件通知
  6. Channel可以用于实现事件的发布和订阅模式,当一个事件发生时,通过Channel将事件通知给所有订阅者。
  7. 同步信号
  8. 可以使用Channel作为信号量,当条件满足时,通过Channel发送一个信号,接收方收到信号后继续执行。
  9. 控制并发任务的启动和结束
  10. 通过Channel可以协调多个goroutine的启动和结束,确保它们按照预定的顺序执行。
  11. 限制并发数
  12. 可以使用带有缓冲的Channel来限制同时运行的goroutine数量。
  13. 同步操作
  14. 可以使用Channel来同步多个操作,确保它们按照预定的顺序进行。
  15. 异步处理
  16. Channel支持异步处理模式,即发送方发送数据后不需要等待接收方处理完成即可继续执行其他任务。

Channel实现原理

chan 使用 hchan 表示,它的传参与赋值始终都是指针形式,每个 hchan 对象代表着一个 chan。

  • hchan 中包含一个缓冲区 buf,它表示已经发送但是还未被接收的数据缓存。buf 的大小由创建 chan 时的参数来决定。qcount 表示当前缓冲区中有效数据的总量,dataqsiz 表示缓冲区的大小,对于无缓冲区通道而言 dataqsiz 的值为 0。如果 qcount 和 dataqsiz 的值相同,则表示缓冲区用完了。
  • 缓冲区表示的是一个环形队列 (如果你不熟悉环形队列,可以看一下 https://www.geeksforgeeks.org/circular-queue-set-1-introduction-array-implementation/)。其中 sendx 表示下一个发送的地址,recvx 表示下一个接收的地址。
  • recvq 表示等待接收的 sudog 列表,一个接收语句执行时,如果缓冲区没有数据而且当前没有别的发送者在等待,那么执行者 goroutine 会被挂起,并且将对应的 sudog 对象放到 recvq 中。
  • sendq 类似于 recvq,一个发送语句执行时,如果缓冲区已经满了,而且没有接收者在等待,那么执行者 goroutine 会被挂起,并且将对应的 sudog 放到 sendq 中。
  • closed 表示通道是否已经被关闭,0 代表没有被关闭,非 0 值代表已经被关闭。
  • lock 用于对 hchan 加锁

hchan 则是 channel 在 golang 中的内部实现。其定义如下:

type hchan struct {qcount   uint           // buffer 中已放入的元素个数dataqsiz uint           // 用户构造 channel 时指定的 buf 大小buf      unsafe.Pointer // bufferelemsize uint16         // buffer 中每个元素的大小closed   uint32         // channel 是否关闭,== 0 代表未 closedelemtype *_type         // channel 元素的类型信息sendx    uint           // buffer 中已发送的索引位置 send indexrecvx    uint           // buffer 中已接收的索引位置 receive indexrecvq    waitq          // 等待接收的 goroutine  list of recv waiterssendq    waitq          // 等待发送的 goroutine list of send waiterslock mutex
}

hchan 中的所有属性大致可以分为三类:

  1. buffer 相关的属性。例如 buf、dataqsiz、qcount 等。 当 channel 的缓冲区大小不为 0 时,buffer 中存放了待接收的数据。使用 ring buffer 实现。
  2. waitq 相关的属性,可以理解为是一个 FIFO 的标准队列。其中 recvq 中是正在等待接收数据的 goroutine,sendq 中是等待发送数据的 goroutine。waitq 使用双向链表实现。
  3. 其他属性,例如 lock、elemtype、closed 等。

channel 的 ring buffer 实现

channel 中使用了 ring buffer(环形缓冲区) 来缓存写入的数据。ring buffer 有很多好处,而且非常适合用来实现 FIFO 式的固定长度队列。

在 channel 中,ring buffer 的实现如下:

hchan 中有两个与 buffer 相关的变量: recvx 和 sendx。其中 sendx 表示 buffer 中可写的 index, recvx 表示 buffer 中可读的 index。 从 recvx 到 sendx 之间的元素,表示已正常存放入 buffer 中的数据。

我们可以直接使用 buf[recvx] 来读取到队列的第一个元素,使用 buf[sendx] = x 来将元素放到队尾。

数据发送:

发送数据分三种情况:

  • 有 goroutine 阻塞在 channel 上,此时 hchan.buf 为空:直接将数据发送给该 goroutine。
  • 当前 hchan.buf 还有可用空间:将数据放到 buffer 里面。
  • 当前 hchan.buf 已满:阻塞当前 goroutine。

第一种情况如下。从当前 channel 的等待队列中取出等待的 goroutine,然后调用 send。goready 负责唤醒 goroutine。

第二种情况比较简单。通过比较 qcount 和 dataqsiz 来判断 hchan.buf 是否还有可用空间。除此之后还需要调整一下 sendx 和 qcount。

数据读取:

从nil channel读取会抛异常。

从 closed channel 接收数据,如果 channel 中还有数据,接着走下面的流程。如果已经没有数据了,则返回默认值。使用 ok-idiom 方式读取的时候,第二个参数返回 false。

当前有发送 goroutine 阻塞在 channel 上,buf 已满:

如果buf size是0,则直接从sender读,否则读取队列的头

lock(&c.lock)if sg := c.sendq.dequeue(); sg != nil {// Found a waiting sender. If buffer is size 0, receive value// directly from sender. Otherwise, receive from head of queue// and add sender's value to the tail of the queue (both map to// the same buffer slot because the queue is full).recv(c, sg, ep, func() { unlock(&c.lock) }, 3)return true, true
}

buf 中有可用数据:

if c.qcount > 0 {// Receive directly from queueqp := chanbuf(c, c.recvx)if raceenabled {raceacquire(qp)racerelease(qp)}if ep != nil {typedmemmove(c.elemtype, ep, qp)}typedmemclr(c.elemtype, qp)c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.qcount--unlock(&c.lock)return true, true
}

buf为空,阻塞

无缓冲的通道只有当发送方和接收方都准备好时才会传送数据,否则准备好的一方将会被阻塞。

有缓存的channel区别在于只有当缓冲区被填满时,才会阻塞发送者,只有当缓冲区为空时才会阻塞接受者。

关闭channel的操作原则上应该由发送者完成,因为如果仍然向一个已关闭的channel发送数据,会导致程序抛出panic。而如果由接受者关闭channel,则会遇到这个风险。

从一个已关闭的channel中读取数据不会报错。只不过需要注意的是,接受者就不会被一个已关闭的channel的阻塞。而且接受者从关闭的channel中仍然可以读取出数据,只不过是这个channel的数据类型的默认值。我们可以通过指定接受状态位来观察接受的数据是否是从一个已关闭的channel所发送出来的数据。

有缓冲channel和无缓冲channel的应用场景

  • 无缓冲channel:同步消息
  • 有缓冲channel:异步消息

为什么Channel会被设计成向已经关闭的channel发送数据会引发panic

Channel 的基本特性和关闭机制

首先,我们需要了解 Channel 的基本特性和关闭机制。Channel 在 Go 中是一个类型安全的队列,它支持两个基本操作:发送(send)和接收(receive)。发送操作将数据放入 Channel,而接收操作从 Channel 中取出数据。通过关闭 Channel(使用 close 函数),发送者可以通知接收者没有更多的数据将被发送。

关闭 Channel 是一个单向操作,意味着一旦 Channel 被关闭,就不能再向其中发送数据。这是 Channel 设计中的一个关键原则,它确保了数据的发送和接收之间的同步和一致性。

为什么向已关闭的 Channel 写数据会引发 Panic?

向一个已经关闭的 Channel 发送数据会引发 panic,这主要是出于以下几个原因:

  1. 保持数据一致性:一旦 Channel 被关闭,接收者应该能够安全地假设不会有更多的数据被发送。如果允许向已关闭的 Channel 发送数据,这将会破坏这种一致性,导致接收者无法准确地判断 Channel 的状态,从而可能引发数据竞争或其他并发问题。
  2. 避免资源泄漏:Channel 的关闭通常意味着与其相关的资源(如内存和 goroutine)可以被释放。如果允许向已关闭的 Channel 发送数据,这些资源可能无法被及时释放,从而导致资源泄漏。
  3. 简化错误处理:通过引发 panic,Go 语言强制开发者在编写代码时处理向已关闭的 Channel 发送数据的情况。这有助于开发者在开发阶段就发现并修复潜在的错误,从而提高程序的健壮性和稳定性。
  4. 符合直观预期:从直觉上讲,向一个已经关闭的通信管道发送数据是不合理的。Go 语言的设计哲学倾向于直观和简洁,因此将这种操作定义为 panic 符合大多数开发者的直观预期。

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

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

相关文章

centos 手动安装libcurl4-openssl-dev库

下载源代码 curl downloadshttps://curl.se/download/ 选择需要下载的版本,我下载的是8.11.0 解压 tar -zxvf curl-8.11.0 查看安装命令 查找INSTALL.md,一般在docs文件夹下 –prefix :指定安装路径(默认安装在/usr/local&…

基于TensorFlow框架的线性回归实现

目录 ​编辑 线性回归简介 TensorFlow简介 线性回归模型的TensorFlow实现 1. 安装TensorFlow 2. 导入必要的库 3. 准备数据 4. 定义模型 5. 定义损失函数 6. 定义优化器 7. 训练模型 8. 评估模型 9. 模型参数的可视化 10. 模型预测的准确性评估 结论 在统计学和…

40分钟学 Go 语言高并发:服务性能调优实战

服务性能调优实战 一、性能优化实战概述 优化阶段主要内容关键指标重要程度瓶颈定位收集性能指标,确定瓶颈位置CPU、内存、延迟、吞吐量⭐⭐⭐⭐⭐代码优化优化算法、并发、内存使用代码执行时间、内存分配⭐⭐⭐⭐⭐系统调优调整系统参数、资源配置系统资源利用率…

云计算vsphere 服务器上添加主机配置

这里是esxi 主机 先把主机打开 然后 先开启dns 再开启 vcenter 把每台设备桌面再vmware workstation 上显示 同上也是一样 ,因为在esxi 主机的界面可能有些东西不好操作 我们选择主机和集群 左边显示172.16.100.200

使用PaddlePaddle实现线性回归模型

目录 ​编辑 引言 PaddlePaddle简介 线性回归模型的构建 1. 准备数据 2. 定义模型 3. 准备数据加载器 4. 定义损失函数和优化器 5. 训练模型 6. 评估模型 7. 预测 结论 引言 线性回归是统计学和机器学习中一个经典的算法,用于预测一个因变量&#xff0…

将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式

文章目录 将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式MathType安装问题MathType30天试用延期MathPage.wll文件找不到问题 将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式 word自带公式编辑器编辑的公式格式: MathType编辑的格式&a…

一文说清:Git创建仓库的方法

0 引言 本文介绍如何创建一个 Git 本地仓库,以及与远程仓库的关联。 1 初始化仓库(git init) 1.1 概述 Git 使用 git init 命令来初始化一个 Git 仓库,Git 的很多命令都需要在 Git 的仓库中运行,所以 git init 是使…

【Linux系统编程】——理解冯诺依曼体系结构

文章目录 冯诺依曼体系结构硬件当代计算机是性价比的产物冯诺依曼的存储冯诺依曼的数据流动步骤冯诺依曼结构总结 冯诺依曼体系结构硬件 下面是整个冯诺依曼体系结构 冯诺依曼结构(Von Neumann Architecture)是现代计算机的基本结构之一,由数…

一、docker简介

一、docker简介 1.1 docker的前世今生 Docker是基于Go语言实现的开源容器项目,诞生于2013年年初,最初的发起者是dotCloud公司,Docker自开源后受到广泛的关注和讨论,目前已有多个相关项目(包括Docker三剑客、Kubernet…

实验三:Mybatis-动态 SQL

目录: 一 、实验目的: 通过 mybatis 提供的各种标签方法实现动态拼接 sql 二 、预习要求: 预习 if、choose、 when、where 等标签的用法 三、实验内容: 根据性别和名字查询用户使用 if 标签改造 UserMapper.xml使用 where 标签进行…

解决Tomcat运行时错误:“Address localhost:1099 is already in use”

目录 背景: 过程: 报错的原因: 解决的方法: 总结: 直接结束Java.exe进程: 使用neststat -aon | findstr 1099 命令: 选择建议: 背景: 准备运行Tomcat服务器调试项目时,程序下…

剖析千益畅行,共享旅游-卡,合规运营与技术赋能双驱下的旅游新篇

在数字化浪潮席卷各行各业的当下,旅游产业与共享经济模式深度融合,催生出旅游卡这类新兴产品。然而,市场乱象丛生,诸多打着 “共享” 幌子的旅游卡弊病百出,让从业者与消费者都深陷困扰。今天,咱们聚焦技术…

三步入门Log4J 的使用

本篇基于Maven 的Project项目&#xff0c; 快速演示Log4j 的导入和演示。 第一步&#xff1a; 导入Log4j依赖 <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.24.2</version&…

node.js基础学习-express框架-静态资源中间件express.static(十一)

前言 在 Node.js 应用中&#xff0c;静态资源是指那些不需要服务器动态处理&#xff0c;直接发送给客户端的文件。常见的静态资源包括 HTML 文件、CSS 样式表、JavaScript 脚本、图片&#xff08;如 JPEG、PNG 等&#xff09;、字体文件和音频、视频文件等。这些文件在服务器端…

Marvell第四季度营收预计超预期,定制芯片需求激增

芯片制造商Marvell Technology&#xff08;美满电子科技&#xff09;&#xff08;MRVL&#xff09;在周二发布了强劲的业绩预告&#xff0c;预计第四季度的营收将超过市场预期&#xff0c;得益于企业对其定制人工智能芯片的需求激增。随着人工智能技术的快速发展&#xff0c;特…

主持人婚礼司仪知识点题库300道;大型免费题库;大风车题库

无偿分享&#xff0c;直接下载 原文件链接&#xff1a;大风车题库-文件 大风车题库网站&#xff1a;大风车题库

WordPress ElementorPageBuilder插件 任意文件读取漏洞复现(CVE-2024-9935)

0x01 产品简介 WordPress Elementor Page Builder插件是一款功能强大的页面构建工具,Elementor Page Builder,即Elementor,是一款广受好评的WordPress页面构建插件。它以其丰富的页面构造组件和灵活拖拽式的部署方式,进一步降低了WordPress构建网站页面的难度。通过Elemen…

人工智能_大模型091_大模型工作流001_使用工作流的原因_处理复杂问题_多轮自我反思优化ReAct_COT思维链---人工智能工作笔记0236

# 清理环境信息&#xff0c;与上课内容无关 import os os.environ["LANGCHAIN_PROJECT"] "" os.environ["LANGCHAIN_API_KEY"] "" os.environ["LANGCHAIN_ENDPOINT"] "" os.environ["LANGCHAIN_TRACING_V…

【开源】A060-基于Spring Boot的游戏交易系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

linux磁盘管理

一&#xff0c;磁盘基础知识 硬盘设备是由大量的扇区组成&#xff0c;每个扇区的容量为512B。其中第一个扇区里面保存着主引导记录和分区表信息&#xff0c;主引导记录占446B&#xff0c;分区表64B&#xff0c;结束符2B&#xff1b;其中分区表每记录一条信息就使用了16B&#…