channel

embedded/2024/10/22 5:41:37/

文章目录

  • channel
    • 1.channe的数据结构
    • 2.channel的操作
      • 1.channel写入
      • 2.channel读取
    • selelect
  • 面试题
    • 1.channel是线程安全的吗?为什么?
    • 2.channel的底层实现原理
    • 3.对channel进行读写关闭操作?
  • 算法题
    • 1.select中case的使用
    • 2.有4个goroutine,按编号1、2、3、4循环打印

channel

1.channe的数据结构

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   // 开始写的位置recvx    uint   // 开始读的位置recvq    waitq  // 添加读的等待队列sendq    waitq  // 添加写的等待队列lock mutex		// runtime.Mutex 保证线程安全
}type waitq struct {first *sudoglast  *sudog
}

对于channel我们可以缓存数据到里面,是因为有一个buf的数组用来缓冲数据,又因为channel可以同时提供读写功能,所以我们有sendx和recvx分别指向下一次写 和 下一次读的位置,buf、sendx、recvx就构成了一个环形数组。

因为channel是用在多个goroutine之间的通信,所以需要一把Mutex来保护读写关闭操作的并发安全,又因为channel提供一个读和写等待队列来帮助goroutine在未完成读写操作后,可以被阻塞挂起(将goroutine放入队列),然后等待channel通信来临再次被唤醒。

type sudog struct {g *g	// 锁定的goroutinenext *sudogprev *sudogelem unsafe.Pointer // data element (may point to stack)acquiretime int64releasetime int64ticket      uint32isSelect boolsuccess boolwaiters uint16parent   *sudog // semaRoot binary treewaitlink *sudog // g.waiting list or semaRootwaittail *sudog // semaRootc        *hchan // channel
}

sudg是对阻塞挂起goroutine的一个封装,用多个sudg来构成等待队列。

2.channel的操作

1.channel写入

  • channel中有读等待goroutine
    • 从recvq取出队列头部的sudg,将要写入的数据拷贝到sudg对应的elem容器上,唤醒sudg绑定的g
  • channel中没有读等待goroutine,并且环形缓冲区数组里面有剩余空间
    • 将数据写到sendx的位置
  • channel中没有读等待goroutine,并且无剩余空间存放数据
    • 获取一个sudg结构,绑定对应的goroutine,channel,ep指针
    • 将sudg放入channel的写等待队列
    • 使用runtime.gopark()挂起当前goroutine
  • 写入的channel为nil
    • 对nil的channel进行写操作,会导致当前goroutine永久性挂起
    • 若在main 函数则直接fatal error
  • channel已经关闭,还想进行写操作
    • 直接panic

2.channel读取

  • channel中有写等待goroutine
    • 从写等待队列里面拿到头部sudog,进入recv流程
    • 如果channel无缓冲区,直接取出sudog里面的数据,并唤醒sudog里的g
    • 如果channel有缓冲区,先尝试读取环形数组recvx对应的元素,并将sudog中的元素写入到缓冲区,唤醒sudog对应的g
  • channel中没有写等待goroutine,并且环形缓冲数组中有剩余元素
    • 读取recvx的数据,recvx++,qcount–
  • channel中没有写等待goroutine,并且环形缓冲数组中无剩余元素
    • 获取一个sudog结构,绑定channel,goroutine,ep指针,将sudog放入channel的读等待队列,挂起当前goroutine
  • 读取的channel为nil
    • main函数中直接fatal error;goroutine中永久挂起
  • channel已经关闭,并且buf里面没有元素
    • 读取对应类型的零值

selelect

核心原理:按照随机顺序执行case,直到某个case操作完成,如果所有case的都没有完成,则看有没有default分支,如果有,就直接走default,防止阻塞。如果没有的话,就将当前goroutine加入到所有case对应channel的等待队列中,等待唤醒。如果当前goroutine被某一个case上的channel操作唤醒后,还需要将当前goroutine从所有case对应channel的等待独队列中剔除。

面试题

1.channel是线程安全的吗?为什么?

是线程安全的。

因为channel的底层数据结构都是用同一把runtime.Mutex来进行保护的,对channel的操作只有读、写、关闭三种操作。在Go语言中,channel主要是让goroutine之间传递数据和进行同步操作,通过channel,可以保证数据一致性和并发安全性。hchan的底层实现中,hchan结构体采用Mutex锁来保护数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据。

2.channel的底层实现原理

channel的底层数据结构叫做runtime.hchan,在hchan拥有一把runtime.Mutex来保证读写关闭操作逻辑的并发安全,通过读写指针和一个buf数组实现了一个环形缓冲队列,让channel拥有存储数据的能力;还拥有读写等待队列,当一个goroutine对channel进行读或写操作,操作无法及时完成的时候,可以进入队列等待,当前goroutine也被runtime.gopark挂起;读写操作也能取出等待队列里面的goroutine,通过runtime.goready将等待中的goroutine唤醒,等待GMP的调度。

3.对channel进行读写关闭操作?

1.对nil的channel进行读和写都会造成永久性阻塞,如果是在main函数 直接fatal error,关闭发生panic。
2.对不为nil,且未关闭的channel操作,读和写都有两种情况:

  • 读操作
    • 成功读取:如果channel中有数据,直接读取channel,如果此时等待队列recvxq里面有goroutine,那么需要将队列头部goroutine写入channel,唤醒这个goroutine;如果channel没有数据,就尝试从写等待队列中读取数据,并做对应的唤醒操作。
    • 阻塞挂起(读操作无法及时完成):channel里面没有数据并且写等待队列为空,则当前goroutine加入等待队列中,并挂起,等待唤醒。
  • 写操作
    • 成功写入:如果channel读等待队列不为空,则取头部goroutine,将数据直接复制给这个头部的goroutine,并将其唤醒,流程结束;否则就尝试将数据写入到channel环形缓冲中。
    • 阻塞挂起(写操作无法及时完成):通道里面buf满了并且等待队列为空,则当前goroutine加入写等待队列中,并挂起,等待唤醒。

3.对已经关闭的channel进行写和关闭操作都会panic,而读取是直到读完channel中剩余的数据,还想读的话,就会获得零值。

算法题

1.select中case的使用

func case1() {c1 := make(chan int)c2 := make(chan int)close(c1)close(c2)select {case <-c1:fmt.Println("c1")case c2 <- 1:fmt.Println("c2")default:fmt.Println("default")}
}

请问以上程序的输出结果?

select中的case调用是随机的,不确定到底先调用哪一个case使用。

func case2() {c := make(chan int, 1)done := falsefor !done {select {case <-c:print(1)c = nilcase c <- 1:print(2)default:print(3)done = true}}
}

有缓冲的channel,在写操作的时候,如果缓冲区未满那么直接写入数据,否则阻塞;在读操作的时候,如果有数据那么直接读取,否则阻塞。

2.有4个goroutine,按编号1、2、3、4循环打印

func main() {ch := make([]chan int, 4)for i, _ := range ch {ch[i] = make(chan int)go func(i int) {for {v := <-ch[i]fmt.Println(v + 1)time.Sleep(time.Second)ch[(i+1)%4] <- (v + 1) % 4}}(i)}ch[0] <- 0select {}
}

http://www.ppmy.cn/embedded/108734.html

相关文章

【MySQL】MySQL Workbench下载安装、环境变量配置、基本MySQL语句、新建Connection

1.MySQL Workbench 下载安装&#xff1a; 进入网址&#xff1a;MySQL :: MySQL Workbench Manual :: 2 Installation &#xff08;1&#xff09;点击“MySQL Workbench on Windows”&#xff08;下载Windows版本&#xff09;&#xff08;2&#xff09;点击“Installing” &…

压缩文件隐写

1、伪加密 &#xff08;1&#xff09;zip伪加密 考点&#xff1a;winhex打开压缩包&#xff1b;搜索504b0102(注意不是文件头部&#xff1b;zip文件头部伪504b0304);从50开始&#xff0c;往后面数第9&#xff0c;10个字符为加密字符&#xff0c;将其设置为0000即可变为无加密状…

代码随想录 刷题记录-27 图论 (4)拓扑排序

一、拓扑排序精讲 题目&#xff1a;117. 软件构建 拓扑排序的背景 本题是拓扑排序的经典题目。 拓扑排序是经典的图论问题。 先说说 拓扑排序的应用场景。 大学排课&#xff0c;例如 先上A课&#xff0c;才能上B课&#xff0c;上了B课才能上C课&#xff0c;上了A课才能上…

2024国赛数学建模备战:灰色预测,国赛数学建模思路代码 模型

2024国赛数学建模ABC题思路模型代码&#xff1a;文末获取&#xff0c;9.5开赛后第一时间更新 许久未更新时间序列分析系列内容。现先推出一期灰色预测 GM(1,1)模型的内容。需明确的是&#xff0c;灰色预测并非典型的时间序列分析方法&#xff0c;然而&#xff0c;它可以应用于…

服务器频频被黑,如何做好安全防护

服务器频频被黑&#xff0c;如何做好安全防护?在数字化时代&#xff0c;服务器作为企业的核心数据资产&#xff0c;其安全性直接关系到企业的生死存亡。随着网络攻击手段的不断升级&#xff0c;服务器频频被入侵的事件屡见不鲜&#xff0c;给企业带来了巨大的损失和风险。如何…

嵌入式架构理论

嵌入式架构简介 嵌入式系统是一个高度定制化的计算系统,嵌入于一个更大的设备或系统中,通常执行特定功能。嵌入式系统无处不在,从智能家居设备到汽车控制系统,几乎所有电子设备中都包含嵌入式技术。 什么是嵌入式系统? 嵌入式系统是由硬件和软件组成的专用计算系统,设…

跟李沐学AI:语言模型

语言模型定义 假设在给定长度为T的文本序列中的词元依次为&#xff0c;可被人做文本序列在时间步t处的观测或标签。在给定这样的文本序列是&#xff0c;语言模型的目标是估计序列的联合概率。 一个理想的与语言模型能够在一次抽取一个词元的情况下基于模型本身生成自然文本。…

Spring中使用ResponseStatusExceptionResolver处理HTTP异常响应码

目录 常用HTTP状态码分类和HttpStatus枚举的对应关系错误请求处理过程定义ResourceBadRequestException类在监测到相应的问题时抛出对应的异常 在日常开发过程中&#xff0c;Spring中默认的HTTP状态处理并不能满足所以场景&#xff0c;可以使用ResponseStatusExceptionResolver…