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

go-channel

设计原理

Go 提及的设计模式就是:不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。

  • 共享内存方式:多个协程共享同一块内存,但是多个协程中读写变量是操作同一块内存,会产生多线程问题的并发问题,所以需要使用互斥锁来实现临界区的互斥访问,会大大影响效率
  • 通信方式(go语言使用):channel通道当做通信的中间件队列,发送方 向channel

先入先出

channel收/发操作都遵循了先进先出的设计,它一共使用了3个队列来实现:

  • 发操作:先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利;(使用写队列hchan.sendq)
    • 接收方会从缓冲区中读取数据,然后唤醒发送方,发送方会尝试向缓冲区写入数据,如果缓冲区已满会重新陷入休眠;
  • 读操作:先从 Channel 读取数据的 Goroutine 会先接收到数据;(使用读队列hchan.recvq)
    • 使用读队列:发送方会向缓冲区中写入数据,然后唤醒接收方,多个接收方会尝试从缓冲区中读取数据,如果没有读取到会重新陷入休眠;

无锁channel(结构体内还是有锁,好像暂未实现)

并发控制可由2种方式实现:

  • 乐观锁:CAS(compare and swap)就是一种乐观锁,默认没有其他线程在修改,当本线程保存数据到内存时判断数据和修改前的原数据是否相同。
  • 悲观锁:redis setnx就是一种悲观锁,默认有其他线程在修改,所以在其他线程拿数据前就阻塞,等待锁释放才能继续操作

乐观锁并没有锁这个变量,而是对原数据进行比较,所以乐观锁只是一种思想无锁channel是使用了乐观锁思想实现的。

数据结构

runtime.hchan结构体

type hchan struct {qcount   uintdataqsiz uintbuf      unsafe.Pointerelemsize uint16closed   uint32elemtype *_typesendx    uintrecvx    uintrecvq    waitqsendq    waitqlock mutex
}
  • qcount:channel里的元素个数
  • dataqsiz:Channel 中的循环队列的容量
  • buf: Channel 的缓冲区数据指针
  • elemsize:元素占内存的大小
  • closed:channel的关闭状态
  • elemtype:元素的类型元数据
  • sendx: Channel 的发送操作处理到的位置
  • recvx:Channel 的接收操作处理到的位置;
  • recvq:接受队列(读队列),当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表
  • sendq:发送队列(写队列),当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表
  • lock:操作通道的锁,同一个时刻只有一个协程可以操作这个chan

队列中存的结构是runtime.sudog

type sudog struct {// The following fields are protected by the hchan.lock of the// channel this sudog is blocking on. shrinkstack depends on// this for sudogs involved in channel ops.g *gnext *sudogprev *sudogelem unsafe.Pointer // data element (may point to stack)// The following fields are never accessed concurrently.// For channels, waitlink is only accessed by g.// For semaphores, all fields (including the ones above)// are only accessed when holding a semaRoot lock.acquiretime int64releasetime int64ticket      uint32// isSelect indicates g is participating in a select, so// g.selectDone must be CAS'd to win the wake-up race.isSelect bool// success indicates whether communication over channel c// succeeded. It is true if the goroutine was awoken because a// value was delivered over channel c, and false if awoken// because c was closed.success boolparent   *sudog // semaRoot binary treewaitlink *sudog // g.waiting list or semaRootwaittail *sudog // semaRootc        *hchan // channel
}
  • g:等待channel的goroutine指针
  • channel:等待的哪个channel
  • elem:等待发送/接收的缓冲区地址下标

channel类型

有缓冲区channel

hchan.buff指向一个数组地址,能存放数据,尽量避免了所有协程有阻塞

  • 写操作:
    • 如果缓冲区内有空间hchan.qcount<hchan.dataqsiz,将数据放入缓冲区,hchan.sendx指向下一个数组下标,唤醒读队列hchan.recvq头部的协程。当前协程不阻塞,继续向下执行代码。
    • 如果缓冲区内没有空间hchan.qcount>=hchan.dataqsiz,当前goroutine阻塞(被挂起_GWating),新创建一个sudogsudog.g指向当前goroutine,sudog变量塞进写队列hchan.sendq
  • 读操作:和写操作差不多,只是操作hchan.recvqhchan.recvx

无缓冲区channel

hchan.buff是个nil值,没有数据存储的区域,肯定会出现阻塞现象

  • 写操作:去hchan.recvq队列中去获取一个正阻塞的协程sudog结构变量
    • 如果hchan.recvq有数据,则根据sudog.g变量去唤醒协程,并向这个协程发送数据
    • 如果hchan.recvq有数据,则创建一个sudog结构体变量,sudog.g变量指向当前协程,放到hchan.sendq队列,当前goroutine阻塞(被挂起_GWating)
  • 读操作:和写操作差不多,只是操作hchan.recvq

唤醒阻塞协程对channel做操作,都是由当前协程通知g0协程做调度

多路select

问题:为什么多个case被阻塞,说明当前g被加到了多个hchan.recvq或者hchan.sendq中,为什么只会执行一个case。

func SendBlock2() {c1 := make(chan int)c2 := make(chan int)// go不能放select后,因为执行顺序的问题,如果放后面在select就挂起了协程,导致没有创建这个协程,也就不可能唤醒当前协程,从而导致死锁go func() {time.Sleep(3 * time.Second)a := <-c1fmt.Println(a)}()select {case c1 <- 2:fmt.Println("case1")case c2 <- 3:fmt.Println("case2")}
}

上例的现象是:打印了case1或者case2,并不会两个都打印。

执行步骤:

  1. 执行case1时给c1加锁,执行case2时给c2加锁
  2. select乱序轮询
  3. g被加到c1和c2的 hchan.sendq中,c1和c2解锁允许其他协程操作这个channel,g被挂起等待
  4. 子协程命中唤醒主协程,命中case1,执行case1操作
  5. 再次对所有case的channel加锁(原因是下一步)
  6. 去c1,c2的recvqsendq遍历删除绑定了当前协程的sudog,因为删除了队列中的等待g,所以g不会被重新唤醒,case2就再也不命中。
  7. c1,c2再次解锁
  8. select结束

使用语法

创建channel

// 方法1,没有分配地址,无法读写chan
var c chan Type// 方法2,分配了地址,设置了size就是有缓冲channel,反之是无缓冲地址
c := make(chan Type [, size])

写channel

c := make(chan Type [, size])// 方法1
c <- val// 方法2
select {case c<-2:// 下一个case c<-2://业务逻辑default://可以避免阻塞
}

读channel

c := make(chan Type [, size])// 方法1
t := <-c// 方法2
t,ok := <-c 	// ok==false表明,chan被关闭// 方法3
select {case <-c://业务逻辑default://可以避免阻塞
}// 方法4
for element := range c {fmt.Println("chan element:", element)
}

关闭channel

close(c)

具体案例

有缓冲区channel

func SendBlock1() {// 创建缓冲区容量是3的通道c := make(chan int, 3)defer close(c)// 创建4个协程往通道里写,会有一个协程阻塞等待for i := 0; i < 4; i++ {go func(i int) {c <- ifmt.Printf("i=%d成功插入chan\n", i)}(i)	// 如果i不使用传参方式,而是使用闭包函数,那么就会发生数据逃逸,i会被存到堆中,栈帧上的i变成指针指向堆,导致协程里的i不一定打印0,1,2,3}time.Sleep(3 * time.Second)//打印,2协程阻塞等待//i=3成功插入chan//i=0成功插入chan//i=1成功插入chan
}

无缓冲区channel

func SendBlock() {c := make(chan int)defer close(c)for i := 0; i < 4; i++ {// 如果i不使用传参方式,而是使用闭包函数,那么就会发生数据逃逸,i会被存到堆中,栈帧上的i变成指针指向堆,导致协程里的i不一定打印0,1,2,3go func(i int) {c <- ifmt.Printf("i=%d成功插入chan\n", i)}(i)}time.Sleep(3 * time.Second)//没有任何打印,因为hchan.recvq没有协程可以唤醒}

使用注意

  • 对一个关闭的channel发送值 panic
  • 对一个关闭的channel接收值,会一直读取成功,直到管道内数据为空
  • 对一个关闭的并且没有值的管道执行接收操作,会得到对应类型的空值
  • 关闭一个已关闭的通道会导致panic
  • 关闭一个chan,会向所有正在监听这个chan的协程都发送一个空元素(元素类型取决于你的chan类型)

死锁:

func f1(channel chan int) {time.Sleep(6 * time.Second)channel <- 20//close(channel)
}func main() {channel := make(chan int)//例1 不会死锁,因为读写都只进行了一次之后就结算了go func() {time.Sleep(6 * time.Second)channel <- 20}()fmt.Println(<-channel)	// 主协程会阻塞等待管道进入数据//例2 不会死锁,因为读写都只进行了一次之后就结算了go f1(channel)fmt.Println(<-channel)//例3 不会死锁,因为读写都只进行了一次之后就结算了go func(channel chan int) {channel <- 20}(channel)fmt.Println(<-channel)//例4 会死锁,主协程会一直等待子进程写入,无法退出,此时需要在子协程加入close(channel),表明自己不会在对协程做操作了go func(channel chan int) {channel <- 20// close(channel) 加上则不会死锁}(channel)for element := range channel {fmt.Println(element)}//例子5,结果是等待3秒后,其中一个消费协程会被死锁,因为他一直在等待channel的数据进入channel := make(chan int, 3)wg := sync.WaitGroup{}wg.Add(3)go func() {defer wg.Done()//fmt.Println("子协程1")fmt.Println("子协程1抢到的" + strconv.Itoa(<-channel))}()go func() {defer wg.Done()//fmt.Println("子协程2")fmt.Println("子协程2抢到的" + strconv.Itoa(<-channel))}()go func() {defer wg.Done()channel <- 20for i := 0; i < 3; i++ {time.Sleep(time.Second)}}()//channel <- 21wg.Wait()
}

相关文章:

go-channel

设计原理 Go 提及的设计模式就是&#xff1a;不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 共享内存方式&#xff1a;多个协程共享同一块内存&#xff0c;但是多个协程中读写变量是操作同一块内存&#xff0c;会产生多线程问题的并发问题&am…...

K8s操作命令

生命周期管理 1. 创建 1. 创建资源 kubectl run 创建并运行一个或多个容器镜像。*创建一个deployment或job来管理容器*。 语法&#xff1a;kubectl run NAME --imageimage [–env“keyvalue”] [–portport] [–replicasreplicas] [–dry-runbool] [–overridesinline-jso…...

【MySQL】 MySQL数据库基础

文章目录 &#x1f431;‍&#x1f453;数据库的操作&#x1f4cc;显示当前的数据库&#x1f4cc;创建数据库&#x1f388;语法&#xff1a;&#x1f388;语法说明&#x1f388;示例&#xff1a; &#x1f334;使用数据库&#x1f38b;删除数据库&#x1f431;‍&#x1f3cd;语…...

vscode 下载安装

vscode 下载安装常用插件 vscode 官网&#xff1a; https://code.visualstudio.com/ 点击右上角 Download 进入下载选择页面 选择自己使用操作对应 CPU 架构 下载 本文使用 x86 架构 64位 windows 系统为例 跳转下载页面 自动 开始下载 下载不开始&#xff1f;试试这个直…...

springboot对接postgres

安装postgres 注意:下述链接方式会自动创建数据库steven_russell,若需要创建其他数据库&#xff0c;可以手动执行命令创建数据库 docker run --name postgres \ -p 5432:5432 \ -e POSTGRES_USERsteven_russell \ -e POSTGRES_PASSWORD123456 \ -itd --privilegedtrue postgre…...

[python 刷题] 242 Valid Anagram

[python 刷题] 242 Valid Anagram 题目&#xff1a; Given two strings s and t, return true if t is an anagram of s, and false otherwise. An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the o…...

算法通过村第七关-树(递归/二叉树遍历)青铜笔记|手撕递归

文章目录 前言1. 递归的特征2. 如何写出好的递归3. 怎么看懂递归的代码总结 前言 提示&#xff1a;我们生活在24小时不眠不休的社会里但是没有24小时不眠不休的身体有些东西必须舍弃 -- 马特海格 这一关&#xff0c;我看要谈论的是递归问题&#xff0c;说到它就牵扯到很多问题了…...

#循循渐进学51单片机#点亮你的LED#not.2

1、深刻理解电容的意义&#xff0c;并且在今后的电路学习过程中要多多注意参考别人电路中去耦电路的处理方法&#xff0c;积累经验。 1&#xff09;电容缓冲电压&#xff0c;抗电磁干扰&#xff1b; 2&#xff09;低频率电容&#xff0c;一般用的最多的是钽电容&#xff0c;电…...

基于Java+SpringBoot+Vue+uniapp点餐小程序(亮点:协同过滤算法、会员系统,购物车结算、在线聊天)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 系统功能结构设计4.2 主要功能描述 五…...

深度学习-全连接神经网络-详解梯度下降从BGD到ADAM - [北邮鲁鹏]

文章目录 参考文章及视频导言梯度下降的原理、过程一、什么是梯度下降&#xff1f;二、梯度下降的运行过程 批量梯度下降法(BGD)随机梯度下降法(SGD)小批量梯度下降法(MBGD)梯度算法的改进梯度下降算法存在的问题动量法(Momentum)目标改进思想为什么有效动量法还有什么效果&…...

数据结构--二叉排序树

目录 二叉排序树的定义 二叉排序树的查找 二叉排序树的插入 二叉排序树的构造 二叉排序树的删除 查找效率分析 回顾 二叉排序树的定义 二叉排序树的查找 查找成功的情况 查找失败的情况 二叉排序树的插入 注意 &#xff08;1&#xff09;二叉排序树不允许出现重复的值…...

Python | 根据子列表中的第二个元素对列表进行排序

在本文中&#xff0c;我们将学习如何根据主列表中存在的子列表的第二个元素对任何列表进行排序。 比如 Input : [[‘rishav’, 10], [‘akash’, 5], [‘ram’, 20], [‘gaurav’, 15]] Output : [[‘akash’, 5], [‘rishav’, 10], [‘gaurav’, 15], [‘ram’, 20]] Input …...

qsort函数详细讲解以及利用冒泡排序模拟实现qsort函数

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.qsort函数 1.1qsort函数的参数 …...

C++QT day6

1> 将之前定义的栈类和队列类都实现成模板类 栈&#xff1a; #include <iostream> #define MAX 128 using namespace std; template<typename T> class Stack_s { private:T *pnew T[MAX];//栈的数组int top;//记录栈顶的变量 public://构造函数Stack_s(int t…...

List与ArrayList

目录 一、List及其使用 1.1 List的概念 1.2 常见接口的介绍 1.3 List的使用 二、线性表和顺序表 2.1 线性表 2.2 顺序表 三、ArrayList介绍 四、ArrayList的使用 4.1 ArrayList构造 4.2 ArrayList的常用方法 4.3 ArrayList的遍历 4.4 ArrayList的扩容机制 五、ArrayList的具…...

【C++】特殊类的设计

文章目录 1. 设计一个类, 不能被拷贝2. 设计一个类, 不能被继承3. 设计一个类, 只能在堆上创建对象3. 设计一个类, 只能在栈上创建对象4. 创建一个类, 只能创建一个对象(单例模式)饿汉模式懒汉模式 1. 设计一个类, 不能被拷贝 &#x1f495; C98方式&#xff1a; 在C11之前&a…...

机器学习:PCA(Principal Component Analysis主成分)降维

参考&#xff1a;PCA降维原理 操作步骤与优缺点_TranSad的博客-CSDN博客 PCA降维算法_偶尔努力翻身的咸鱼的博客-CSDN博客 需要提前了解的数学知识&#xff1a; 一、PCA的主要思想 PCA&#xff0c;即主成分分析方法&#xff0c;是一种使用最广泛的数据降维算法。PCA的主要思想…...

linux服务器slab缓存回收方案设计

背景 自己写的回收slab内存ko,insmod报错“shrink_slab:unknown symbol _x86_indirect_thunk_rax(err 0)””; 分析 1.名词解释 在 x86 架构中,函数调用通常使用 call 指令来直接跳转到目标函数的地址。但是,当需要通过函数指针或动态链接调用函数时,就需要使用__x86_…...

Apache Spark 的基本概念

Apache Spark 是一种快速、可扩展、通用的数据处理引擎。它是一种基于内存的计算框架&#xff0c;支持分布式数据处理、机器学习、图形计算等多种计算任务。与传统的 Hadoop MapReduce 相比&#xff0c;Spark 具有更高的性能和更广泛的应用场景。 Spark 中的基本概念包括&…...

通讯协议介绍CoAP 协议解析

目录 1 通讯协议 2 TCP/IP 网络模型 2.1 TCP协议 2.1.1 TCP 连接过程 2.1.2 TCP 断开连接 2.1.3 TCP协议特点 2.2 UDP协议 2.2.1 UDP 协议特点 3 应用层协议简介 3.1 HTTP 协议 3.2 CoAP 协议 3.3 MQTT 协议 4 CoAP 协议详解 4.1 REST 风格 4.2 CoAP 首部分析 4…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...