Golang的channel

news2025/3/2 4:24:45

目录

基本使用

 channel 数据结构

阻塞的协程队列

协程节点

构建 channel

写流程

读流程

非阻塞与阻塞

closechan(关闭)


基本使用

创建无缓存 channel

c := make(chan int) //创建无缓冲的通道 c


c := make(chan int,0) //创建无缓冲的通道 c

创建有缓存 channel

c := make(chan int, 3) //创建无缓冲的通道 c

例子:

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int, 3) //创建有缓冲的通道 c

	//内置函数 len 返回未被读取的缓冲元素数量,cap 返回缓冲区大小
	fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))

	go func() {
		defer fmt.Println("子go程结束")

		for i := 0; i < 3; i++ {
			c <- i
			fmt.Printf("子go程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
		}
	}()

	time.Sleep(2 * time.Second) //延时2s

	for i := 0; i < 3; i++ {
		num := <-c //从c中接收数据,并赋值给num
		fmt.Println("num = ", num)
	}

	fmt.Println("main进程结束")
}

 channel 数据结构

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters
    
    lock mutex
}

hchan:channel 数据结构

• qcount:当前 channel 中存在多少个元素;

• dataqsize: 当前 channel 能存放的元素容量;

• buf:channel 中用于存放元素的环形缓冲区;

• elemsize:channel 元素类型的大小;

• closed:标识 channel 是否关闭;

• elemtype:channel 元素类型;

• sendx:发送元素进入环形缓冲区的 index;

• recvx:接收元素所处的环形缓冲区的 index;

• recvq:因接收而陷入阻塞的协程队列;

• sendq:因发送而陷入阻塞的协程队列;

 lock mutex 锁

阻塞的协程队列

type waitq struct {
    first *sudog
    last  *sudog
}

waitq:阻塞的协程队列

• first:队列头部

• last:队列尾部

协程节点

sudog:用于包装协程的节点

type sudog struct {
    g *g

    next *sudog
    prev *sudog
    elem unsafe.Pointer // data element (may point to stack)

    isSelect bool

    c        *hchan 
}

• g:goroutine,协程;

• next:队列中的下一个节点;

• prev:队列中的前一个节点;

• elem: 读取/写入 channel 的数据的容器;

• isSelect:标识当前协程是否处在 select 多路复用的流程中;

• c:标识与当前 sudog 交互的 chan.

构建 channel

func makechan(t *chantype, size int) *hchan {
    elem := t.elem

    // ...
    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))
    }

    var c *hchan
    switch {
    case mem == 0:
        // Queue or element size is zero.
        c = (*hchan)(mallocgc(hchanSize, nil, true))
        // Race detector uses this location for synchronization.
        c.buf = c.raceaddr()
    case elem.ptrdata == 0:
        // Elements do not contain pointers.
        // Allocate hchan and buf in one call.
        c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
        c.buf = add(unsafe.Pointer(c), hchanSize)
    default:
        // Elements contain pointers.
        c = new(hchan)
        c.buf = mallocgc(mem, elem, true)
    }

    c.elemsize = uint16(elem.size)
    c.elemtype = elem
    c.dataqsiz = uint(size)
    
    lockInit(&c.lock, lockRankHchan)

    return
}

写流程

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
    // ...
	//加锁
    lock(&c.lock)

    // ...
	//写时存在阻塞读协程
    if sg := c.recvq.dequeue(); sg != nil {
        // Found a waiting receiver. We pass the value we want to send
        // directly to the receiver, bypassing the channel buffer (if any).
        send(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true
    }
    //写时不存在阻塞读协程,且缓冲区不满仍有空间
    if c.qcount < c.dataqsiz {
        // Space is available in the channel buffer. Enqueue the element to send.
        qp := chanbuf(c, c.sendx)
        typedmemmove(c.elemtype, qp, ep)
        c.sendx++
        if c.sendx == c.dataqsiz {
            c.sendx = 0
        }
        c.qcount++
        unlock(&c.lock)
        return true
    }
    //写时不存在阻塞读协程,且缓冲区满了没有空间
    // ...
    gp := getg()
    mysg := acquireSudog()
    mysg.elem = ep
    mysg.g = gp
    mysg.c = c
    gp.waiting = mysg
    c.sendq.enqueue(mysg)
    
    atomic.Store8(&gp.parkingOnChan, 1)
    gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
    
    gp.waiting = nil
    closed := !mysg.success
    gp.param = nil
    mysg.c = nil
    releaseSudog(mysg)
    return true
}

总结: 

1.当写时存在阻塞读协程,我们直接用 

2.当写时不存在阻塞读协程,且缓冲区不满仍有空间时,我们直接加入环形缓冲区中

3.当写时不存在阻塞读协程,且缓冲区满了没用空间时,加入阻塞写协程队列中

注意:

1.有阻塞读协程和缓冲区满之间只有一个条件符合

2.对于未初始化的 chan,写入操作会引发死锁

3.对于已关闭的 chan,写入操作会引发 panic.

读流程

读流程与写流程差不多,不同点:

1.加入的是阻塞读队列

2.当环形缓冲区有和无数据时会有不同的操作

注意:

1.读空channel, park挂起,引起死锁

2.channel 已关闭且内部无元素,直接解锁返回

非阻塞与阻塞

区别:

非阻塞模式下,读/写 channel 方法通过一个 bool 型的响应参数,用以标识是否读取/写入成功.

• 所有需要使得当前 goroutine 被挂起的操作,在非阻塞模式下都会返回 false

• 所有是的当前 goroutine 会进入死锁的操作,在非阻塞模式下都会返回 false

• 所有能立即完成读取/写入操作的条件下,非阻塞模式下会返回 true.

何时进入非阻塞

默认情况下,读/写 channel 都是阻塞模式,只有在 select 语句组成的多路复用分支中,

与 channel 的交互会变成非阻塞模式:

在sudog:用于包装协程的节点

• isSelect:标识当前协程是否处在 select 多路复用的流程中;
 

closechan(关闭)

func closechan(c *hchan) {
    if c == nil {
        panic(plainError("close of nil channel"))
    }

    lock(&c.lock)
    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("close of closed channel"))
    }

    c.closed = 1

    var glist gList
    // release all readers
    for {
        sg := c.recvq.dequeue()
        if sg == nil {
            break
        }
        if sg.elem != nil {
            typedmemclr(c.elemtype, sg.elem)
            sg.elem = nil
        }
        gp := sg.g
        gp.param = unsafe.Pointer(sg)
        sg.success = false
        glist.push(gp)
    }

    // release all writers (they will panic)
    for {
        sg := c.sendq.dequeue()
        if sg == nil {
            break
        }
        sg.elem = nil
        gp := sg.g
        gp.param = unsafe.Pointer(sg)
        sg.success = false
        glist.push(gp)
    }
    unlock(&c.lock)

    // Ready all Gs now that we've dropped the channel lock.
    for !glist.empty() {
        gp := glist.pop()
        gp.schedlink = 0
        goready(gp, 3)

关闭未初始化过的 channel 会 panic;

• 加锁;

• 重复关闭 channel 会 panic;

• 将阻塞读协程队列中的协程节点统一添加到 glist;

• 将阻塞写协程队列中的协程节点统一添加到 glist;

• 唤醒 glist 当中的所有协程.

防止还有协程挂起,没有被唤醒的资源浪费

参考:小徐先生1212 -- Golang Channel实现原理

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(三十九)Vue之集中式的状态管理机制Vuex

目录 概念vuex的核心概念State&#xff08;状态&#xff09;Getters&#xff08;获取器&#xff09;Mutations&#xff08;突变&#xff09;Actions&#xff08;动作&#xff09; 搭建vuex环境基本使用getters的使用 上一篇&#xff1a;&#xff08;三十八&#xff09;Vue之插槽…

JDK8新特性【接口新特征、lambda语法、Supplier、Consumer、Function、Predicate】

目录 一、关于接口的新特性1.1 jdk1.8之前的接口重要特性1.2 JDK8以后代码演示 1.3 总结通过代码演示发现作用 二、Lambda表达式[重点]2.1 将匿名内部类写法改写为lambda写法2.2 语法特点能够写成lambda形式的的前提语法特征代码演示深入理解lambda 2.3 总结 三、函数式接口3.1…

社区疫情管理系统

社区疫情管理系统 java web源码 增删改查 适合新手有导入视频 java项目 ssm疫情防控登记管理社区疫情管理源码jsp项目 技术 ssm 源码➕数据库文件➕eclipse导入视频

基于C#开发web网页管理系统模板流程-打包发布

点击返回目录-> 基于C#开发web网页管理系统模板流程-总集篇-CSDN博客 前言 本系列中&#xff0c;作为开发者我们通过ASP.net Web模板设计网页&#xff0c;网页的任何设计、源代码都是直接可见的&#xff0c;在实际应用开发中&#xff0c;显然这些都是商业、公司机密 通过打包…

Vue基础知识:异步DOM更新是什么?$nextTick是什么?到底应该如何使用。什么是同步?什么是异步?

要先了解异步dom更新是什么就必须先了解&#xff0c;什么是同步&#xff1f;什么是异步&#xff1f; 1.什么是同步&#xff1f;什么是异步&#xff1f; 同步&#xff08;Synchronous&#xff09;&#xff1a; 同步操作是按照代码的顺序执行的&#xff0c;每个操作都必须等待上…

SAP OB52 财务账期月结月底月初开关

公告&#xff1a;周一至周五每日一更&#xff0c;周六日存稿&#xff0c;请您点“关注”和“在看”&#xff0c;后续推送的时候不至于看不到每日更新内容&#xff0c;感谢。 这是一条刮刮乐&#xff0c;按住全部选中&#xff1a;点关注的人最帅最美&#xff0c;欢迎&#xff1…

vue2+echarts:echarts在dialog弹框中不显示的解决方案

重点是open方法里使用$nextTick拿到最新的dom&#xff0c;在里面加载echarts //html <el-button click.stop"getIfStorage"></el-button><el-dialog title"图表数据" :visible.sync"ifStorageShowOpen" open"open()" …

嵌入式系统中判断大小端的方法与实现

第一&#xff1a;大小端基本分析 程序判断计算机是大端的还是小端的&#xff0c;判断的思路是确定一个多字节的值(下面使用的是4字节的整数)&#xff0c;将其写入内存(即赋值给一个变量)&#xff0c;然后用指针取其首地址所对应的字节(即低地址的一个字节)&#xff0c;判断该字…

Photoshop中颜色与色调的调整

Photoshop中颜色与色调的调整 Photoshop中的颜色模式RGB模式灰度模式位图模式索引模式CMYK模式Lab模式 Photoshop中的颜色/色调调整命令颜色/色调调整命令的分类亮度/对比度调整命令色阶命令曲线命令曝光度命令自然饱和度命令色相/饱和度命令色彩平衡命令照片滤镜调整命令通道混…

[leetcode]swap-nodes-in-pairs

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:ListNode* swapPairs(ListNode* head) {ListNode* dummyHead new ListNode(0);dummyHead->next head;ListNode* temp dummyHead;while (temp->next ! nullptr && temp->next->next !…

单链表实现:从理论到代码

✨✨欢迎&#x1f44d;&#x1f44d;点赞☕️☕️收藏✍✍评论 个人主页&#xff1a;秋邱博客 所属栏目&#xff1a;C语言&#xff0c;数据结构 ​ 前言 前面学习了顺序表&#xff0c;顺序表优点&#xff1a; 可以随机访问元素&#xff0c;通过索引能快速定位到元素。存储密…

Python界面编辑器Tkinter布局助手 使用体验

一、发现 我今天在网上搜关于Python Tkinter方面的信息时&#xff0c;发现了Python界面编辑器 Tkinter布局助手 的使用说明。 https://blog.csdn.net/weixin_52777652/article/details/135291731?spm1001.2014.3001.5506 这个编辑器是个开源的项目&#xff0c;个人用户可以…

Python学习笔记7:入门知识(七)

前言 之前说过我更换了新的学习路线&#xff0c;现在是根据官方文档和书籍Python crash course来进行学习的&#xff0c;在目前的学习中&#xff0c;对于之前的知识有一些遗漏&#xff0c;这里进行补充。 学习资料有两个&#xff0c;书籍中文版PDF&#xff0c;关注我私信发送…

Lua实现自定义函数面向对象编程

本文目录 1、引言2、原理3、实例4、层析验证 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在现代软件开发中&#xff0c;面向对象编程&#xff08;OOP&#xff09;已经成为一种广泛使用的编程范式…

nodejs 某音douyin网页端搜索接口及x_bogus、a_bogus(包含完整源码)(2024-06-13)

前言 x_bogus或a_bogus算法大概是对数据、ua、时间戳、浏览器的几个指纹进行计算&#xff0c;拿到一个110位大数组&#xff0c;然后转字符&#xff0c;在头部再添加十二位随机字符&#xff0c;再进行魔改的base64加密。 问&#xff1a;抖音的x_bogus、a_bogus值有什么用&#x…

幸狐RV1106开发板烧录Ubuntu系统与配置SDK,RV1106 LuckFox Pico Max——最新的操作

资料&#xff1a;上手教程 | LUCKFOX WIKI 以及SDK内的文档资料 开发板型号&#xff1a;RV1106 LuckFox Pico Max 烧录系统&#xff1a; Ubuntu 虚拟机系统&#xff1a;Ubuntu 20.04&&Ubuntu22.04 PC系统&#xff1a;win11 占用空间&#xff1a;大概15G 本文主要记…

idea有这个类却报红,无法用快捷键找到

idea有这个类却报红&#xff0c;无法用快捷键找到&#xff0c;但是项目启动却没有任何问题&#xff0c;严重影响到了开发效率&#xff0c;关idea 重新打开没有用。 找了一圈&#xff0c;办法如下&#xff1a; 1、点击左上角的 File—>Invalidate Caches/Restar 2、点击 In…

【Linux】进程控制3——进程程序替换

一&#xff0c;前言 创建子进程的目的之一就是为了代劳父进程执行父进程的部分代码&#xff0c;也就是说本质上来说父子进程都是执行的同一个代码段的数据&#xff0c;在子进程修改数据的时候进行写时拷贝修改数据段的部分数据。 但是还有一个目的——将子进程在运行时指向一个…

Python私教张大鹏 Vue3整合AntDesignVue之DatePicker 日期选择框

案例&#xff1a;选择日期 <script setup> import {ref} from "vue";const date ref(null) </script> <template><div class"p-8 bg-indigo-50 text-center"><a-date-picker v-model:value"date"/><a-divide…

visio绘制直线

1、右键打开绘图工具 2、选择线条 3、画直线、画横线