1.golangchanrange
2.golang chan 最详细原理剖析,源码全面源码分析!源码看完不可能不懂的源码!
golangchanrange
åéçåºæ¬ç±»åä¸é¶å¼-GOLANG
ç±»åçé¶å¼ï¼å½ä¸ä¸ªç±»å声æäºä½æ¯å¹¶æ²¡æèµå¼ï¼ä¼æä¸ä¸ªç¸å¯¹åºçé»è®¤å¼ç»è¿ä¸ªç±»åã对äºå çå¼å ¥ï¼ä¸å®è¦ç¨åå¼å·ï¼åéçå符串声æä¹è¦ç¨åå¼å·å èµ·æ¥ï¼ä¸ç¶ç¨åºè¿è¡ä¼æ¥éã
æ¯è¾ç两个åéç±»åå¿ é¡»ç¸çãèä¸ï¼golang没æéå¼ç±»å转æ¢ï¼æ¯è¾ç两个åéå¿ é¡»ç±»åå®å ¨ä¸æ ·ï¼ç±»åå«åä¹ä¸è¡ãå¦æè¦æ¯è¾ï¼å åç±»å转æ¢åæ¯è¾ãå¤åç±»åæ¯é个å段ï¼é个å ç´ æ¯è¾çã
æéåéçé¶å¼æ¯â空âï¼è®°ä¸ºNULLï¼ã尽管NULLçå¼ä¸0ç¸åï¼ä½æ¯ä¸¤è æä¹ä¸åã
ä¸åçè¯è¨ãä¸åçç¼è¯å¨ä¼æä¸åçåéç±»åã
golangçå¸å°ç±»ååéé»è®¤ç©ºå¼æ¯falseï¼å³è®¤ä¸ºæ æ交å¼ï¼æ以ä¸é¢çè¦æ交BlogçPublishedå¼ä¸ºfalseæ¯æ交ä¸æåçã
Golangå®ç°ç产è åæ¶è´¹è1ãgolangå®ç°å¤ç产è å¤æ¶è´¹è ï¼è¿è¡ç»æï¼å¯ä»¥çåºï¼ç¨golangå®ç°ç产è æ¶è´¹è é常ç®åï¼PVæä½ä¸éè¦åç§å é解éï¼å¥¥å¦å°±å¨äºCSP模åï¼å³golangæå¡çç¨é信代æ¿å ±äº«å åã
2ãNSQæ¯ä¸ä¸ªåºäºGoè¯è¨çåå¸å¼å®æ¶æ¶æ¯å¹³å°ï¼å®åºäºMITå¼æºåè®®åå¸ï¼ç±bitlyå ¬å¸å¼æºåºæ¥çä¸æ¬¾ç®åæç¨çæ¶æ¯ä¸é´ä»¶ã
3ãGoè¯è¨ä¸çgo-channelæ¯ä¸ç§å¾å¸¸ç¨ç并åéä¿¡æ¹å¼ï¼éè¿å®å¯ä»¥å®ç°åç¨ä¹é´çæ°æ®ä¼ è¾ä¸åæ¥ï¼å¸¸å¸¸ç¨äºåç¨æ± ãäºä»¶é©±å¨ãç产è -æ¶è´¹è 模å¼çåºæ¯ã
4ãPRODUCER_SPEEDå³å®ç产çé度ï¼è¶å¤§è¶æ ¢ï¼å¯ä»¥èªå·±ä¿®æ¹ï¼æçä¾åéï¼ç产è ç产ä¸ä¸ªéæºæ°ã
5ãç¨PãVæä½å®ç°å¤ä¸ªç产è -æ¶è´¹è é®é¢ãæµè¯æ°æ®æ件å æ¬n+lè¡æµè¯æ°æ®ï¼ç¬¬ä¸è¡è¯´æå 个临çåºï¼å ¶ä½nè¡åå«æè¿°å建çn个线ç¨æ¯ç产è è¿æ¯æ¶è´¹è ï¼ä»¥åç产产å(ææ¶è´¹äº§å)çæ¶é´ã...ç¨PãVæä½å®ç°å¤ä¸ªç产è -æ¶è´¹è é®é¢ã
6ãèç产è åæ¶è´¹è æ°éçå¤å°é½ä¸å½±å该类é®é¢ç解å³ï¼åªè¦æç¡®äºç产è åæ¶è´¹è åèªçæ°éï¼ä¸ä¸ªä¿¡å·éå°±éè¦è·éçååï¼å æ¤ä¿¡å·éæ¯å¯ä»¥è®¾ç½®çæ´å¤ææ´å°ï¼è¿æ ·æ´æå©äºè§£å³è¿ç§é®é¢ã
æä¹ä½¿ç¨golangçchannelå广æ
1ã让æ¯ä¸ªworkerçå¬ä¸æç广æchannelï¼å¹¶ä¸ä»ä¸»channelä¸æ´¾åæ¶æ¯å°æ¯ä¸ä¸ªä¸æç广æchannelä¸ã
2ãä¿è¯channelåå ¥å读åæ°æ®æ¶çº¿ç¨å®å ¨çéï¼lockç¯å½¢æ°ç»ä½ä¸ºchannelçç¼å²åºæ°ç»çé¿åº¦å°±æ¯å®ä¹channnelæ¶channelçç¼å²å¤§å°å¨hchanä¸å æ¬äºè¯»/åçå¾ éåï¼waitqæ¯ä¸ä¸ªååéåï¼å æ¬äºä¸ä¸ªå¤´ç»ç¹åå°¾èç¹ã
3ã使ç¨å ç½®å½æ°makeå建æ ç¼å²åç¼å²ééãmakeç第ä¸ä¸ªåæ°éè¦å ³é®åchanï¼ç¶åæ¯ééå 许交æ¢çæ°æ®ç±»åãè¿æ¯å°å¼åéå°ééç代ç åéè¦ä½¿ç¨-è¿ç®ç¬¦ï¼è¯æ³ä¸ä¸ªå å«5个å¼çç¼å²åºçå符串类åçgoroutine1ééã
å¦ä½ç解Golangä¸çrangeè¯å¥1ãrangeæ¯ä¸ä¸ªstatementï¼ä¸æ¯å½æ°ï¼åforä¸èµ·ä½¿ç¨ï¼ç¨äºéåsliceï¼arrayï¼mapï¼stringæè channelãrangeçè¿åå¼æä¿©ï¼åå³äºéåé£ç§æ°æ®ç»æãå¯ä¸æ³¨æçå°æ¹æ¯ï¼éåmapæ¶ï¼å ç´ ç顺åºæ¯éæºçã
2ãå 为éåmyviewlistæ¶ï¼å®é ä¸æ¯å¤å¶myviewlistæ°ç»/åçä¸çå ç´ å°å±é¨åévwä¸ãå±é¨åévwçå°åå½ç¶åmyviewlist[0]çå°åä¸ä¸æ ·ã
3ãGoè¯è¨ä¸è¯»åmapæ两ç§è¯æ³ï¼å¸¦commaåä¸å¸¦commaãå½è¦æ¥è¯¢çkeyä¸å¨mapéï¼å¸¦commaçç¨æ³ä¼è¿åä¸ä¸ªboolååéæ示keyæ¯å¦å¨mapä¸ï¼èä¸å¸¦commaçè¯å¥åä¼è¿åä¸ä¸ªvalueç±»åçé¶å¼ã
4ãpythonä¸rangeæ¯å ³é®åãæ ¹æ®æ¥è¯¢ç¸å ³å ¬å¼ä¿¡æ¯æ¾ç¤ºï¼rangeæ¯pythonä¸çå ç½®å½æ°ï¼rangeï¼ï¼æ¯ç¨æ¥è¿åä¸ä¸ªè¿ä»£å¯¹è±¡ï¼èä¸ä» ä» æ¯è®¡ç®æä¸ä¸ªå¼ï¼å¨å®é 使ç¨å½ä¸rangeç»å¸¸æé forè¯å¥ä½¿ç¨ã
5ãæ¯éåæ°ç»çææï¼print(i)è¾åºiæ¶åºè¯¥ãå½range(1ï¼5)éåªæä¸ä¸ªéå·æ¶ï¼åæéåä»1å¼å§å°5ç»æforiinrange(1ï¼5)print(i)è¾åºiæ¶åºè¯¥ã
请Golang深度ç¨æ·è¯´è¯´,源码ç°å¨Golangçæ§è½å¯ä»¥åCæ¯å1ãæ¨å¥½ï¼ææ¥ä¸ºæ¨è§£æ»ä½ä¸çgoè¦æ¯c/c++æ ¢2-3åçæ ·å.ææè§è¿ç§å¯¹æ¯æä¹ä¸å¤§ï¼å ä¸ºæ ¹æ®è§£å³çé®é¢ä¸åä¸åè¯è¨ç¸å·®å¾å¤§ï¼æ´å¤çæ¶åå¼åæçéè¦æ§è¿å¤§äºè¿è¡æç.å¦ææçåç没è½å¸®å©æ¨ï¼è¯·ç»§ç»è¿½é®ã
2ã好ç¨ï¼ä¼ç¹å¦ä¸ï¼å¹¶åç®åãæçé«å½æ°å¯ä»¥è¿åå¤ä¸ªåæ°åå¾åæ¶ï¼ç¸æ¯c/c++ã
3ãå½ç¶ï¼å¨goè°ç¨cçæ¶åï¼å¦æèæ¶æ¯è¾é¿ï¼ä¼å¯¹goçè¿è¡æ¶é æä¸äºå¯ä½ç¨ï¼å¨cåè°goçæ¶åï¼goçè¿è¡æ¶ä¹æå¯è½é»å¡cçåè°çº¿ç¨ãä½goçè¿è¡æ¶å·²ç»æ¯è¾æçï¼å æ¤æè§å¾å®å¯¹è¿ä¸ªé®é¢çè´¡ç®ä¸å¤§ã
4ãGolangï¼Golangçè¯è¨ç¹æ§æ¯è¾ç®ååç´ç½ï¼ä½æ¯å´è¿åå¨ä¸äºæ°çæ¦å¿µãæ¯å¦æ¥å£åGoroutines并åï¼è¿éè¦ä¸å®çå¦ä¹ åæ¶é´ã
5ãGoè¯è¨ï¼å¨åè½ä¸æ²¡æè¶ è¿C/C++ï¼éç¨è 为没æC/C++ç»éªçå¼åè ï¼å¼ååºæ¥è¿Cæççç¨åºã对äºå·²ç»çç»ææ¡C/C++çå¼åè æ¥è¯´ï¼Goè¯è¨æ²¡æä¼å¿ï¼è¿è¦éå¦è¯æ³ï¼éåºå¼åç¯å¢ï¼ææ¾æ¯ä¸ç¬¦åæççã
6ãæ以æ¬æç»ä¸æ¯å¨è¯´Golangæ¯æ¯å ¶ä»è¯è¨æ´å¥½çè¯è¨ãGolangåªæ¯æå¼å¾æ¨èçè¯è¨ï¼å°¤å ¶éåå¿«éæé¿ä¸çå端ç åå¢éã
golang chan 最详细原理剖析,全面源码分析!源码看完不可能不懂的源码找茬外挂源码!
大纲
概述
chan 是源码 golang 的核心结构,是源码与其他高级语言区别的显著特色之一,也是源码 goroutine 通信的关键要素。尽管广泛使用,源码但对其深入理解的源码人却不多。本文将从源码编译器的源码视角,全面剖析 channel 的源码用法。
channel 的源码本质
从实现角度来看,golang 的源码 channel 实质上是环形队列(ringbuffer)的实现。我们将 chan 称为管理结构,gitea 源码channel 中可以放置任何类型的对象,称为元素。
channel 的使用方法
我们从 channel 的使用方式入手,详细介绍 channel 的使用方法。
channel 的创建
创建 channel 时,用户通常有两种选择:创建带有缓冲区和不带缓冲区的 channel。这对应于 runtime/chan.go 文件中的 makechan 函数。
channel 入队
用户使用姿势:对应函数实现为 chansend,位于 runtime/chan.go 文件。
channel 出队
用户使用姿势:对应函数分别是 chanrecv1 和 chanrecv2,位于 runtime/chan.go 文件。
结合 select 语句
用户使用姿势:对应函数实现为 selectnbsend,位于 runtime/chan.go 文件中。
结合 for-range 语句
用户使用姿势:对应使用函数 chanrecv2,位于 runtime/chan.go 文件中。opensees源码
源码解析
以上,我们通过宏观的用户使用姿势,了解了不同使用姿势对应的不同实现函数,接下来将详细分析这些函数的实现。
makechan 函数
负责 channel 的创建。在 go 程序中,当我们写类似 v := make(chan int) 的初始化语句时,就会调用不同类型对应的初始化函数,其中 channel 的初始化函数就是 makechen。
runtime.makechan
定义原型:
通过这个,我们可以了解到,声明创建一个 channel 实际上是得到了一个 hchan 的指针,因此 channel 的核心结构就是基于 hchan 实现的。
其中,t 参数指定元素类型,morning源码size 指定 channel 缓冲区槽位数量。如果是带缓冲区的 channel,那么 size 就是槽位数;如果没有指定,那么就是 0。
makechan 函数执行了以下两件事:
1. 参数校验:主要是越界或 limit 的校验。
2. 初始化 hchan:分为三种情况:
所以,我们看到除了 hchan 结构体本身的内存分配,该结构体初始化的关键在于四个字段:
hchan 结构
makechan 函数负责创建了 chan 的核心结构-hchan,接下来我们将详细分析 hchan 结构体本身。
在 makechan 中,初始化时实际上只初始化了四个核心字段:
我们使用 channel 时知道,channel 常常会因为两种情况而阻塞:1)投递时没有空间;2)取出时还没有元素。
从以上描述来看,就涉及到 goroutine 阻塞和 goroutine 唤醒,这个功能与 recvq,ecdh源码sendq 这两个字段有关。
waitq 类型实际上是一个双向列表的实现,与 linux 中的 LIST 实现非常相似。
chansend 函数
chansend 函数是在编译器解析到 c <- x 这样的代码时插入的,本质上就是把一个用户元素投递到 hchan 的 ringbuffer 中。chansend 调用时,一般用户会遇到两种情况:
接下来,我们看看 chansend 究竟做了什么。
当我们在 golang 中执行 c <- x 这样的代码,意图将一个元素投递到 channel 时,实际上调用的是 chansend 函数。这个函数分几个场景来处理,总结来说:
关于返回值:chansend 返回值标明元素是否成功入队,成功则返回 true,否则 false。
select 的提前揭秘:
golang 源代码经过编译会变成类似如下:
而 selectnbasend 只是一个代理:
小结:没错,chansend 功能就是这么简单,本质上就是一句话:将元素投递到 channel 中。
chanrecv 函数
对应的 golang 语句是 <- c。该函数实现了 channel 的元素出队功能。举个例子,编译对应一般如下:
golang 语句:
对应:
golang 语句(这次的区别在于是否有返回值):
对应:
编译器在遇到 <- c 和 v, ok := <- c 的语句时,会换成对应的 chanrecv1,chanrecv2 函数,这两个函数本质上都是一个简单的封装,元素出队的实现函数是 chanrecv,我们详细分析这个函数。
chanrecv 函数的返回值有两个值,selected,received,其中 selected 一般作为 select 结合的函数返回值,指明是否要进入 select-case 的代码分支,received 表明是否从队列中成功获取到元素,有几种情况:
selectnbsend 函数
该函数是 c <- v 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsend 函数,如下:
对应编译函数逻辑如下:
selectnbsend 本质上也就是个 chansend 的封装:
chansend 的内部逻辑上面已经详细说明过,唯一不同的就是 block 参数被赋值为 false,也就是说,在 ringbuffer 没有空间的情况下也不会阻塞,直接返回。划重点:chan 在这里不会切走执行权限。
selectnbrecv 函数
该函数是 v := <- c 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsrecv 函数,如下:
对应编译函数逻辑如下:
selectnbrecv 本质上也就是个 chanrecv 的封装:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。
selectnbrecv2 函数
该函数是 v, ok = <- c 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbrecv2 函数,如下:
对应编译函数逻辑如下:
selectnbrecv2 本质上是个 chanrecv 的封装,只不过返回值不一样而已:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。selectnbrecv2 与 selectnbrecv 函数的不同之处在于还有一个 ok 参数指明是否获取到了元素。
chanrecv2 函数
chan 可以与 for-range 结合使用,编译器会识别这种语法。如下:
这个本质上是个 for 循环,我们知道 for 循环关键是拆分成三个部分:初始化、条件判断、条件递进。
那么在我们 for-range 和 chan 结合起来之后,这三个关键因素又是怎么理解的呢?简述如下:
init 初始化:无
condition 条件判断:
increment 条件递进:无
当编译器遇到上面 chan 结合 for-range 写法时,会转换成 chanrecv2 的函数调用。目的是从 channel 中出队元素,返回值为 received。首先看下 chanrecv2 的实现:
chan 结合 for-range 编译之后的伪代码如下:
划重点:从这个实现中,我们可以获取一个非常重要的信息,for-range 和 chan 的结束条件只有这个 chan 被 close 了,否则一直会处于这个死循环内部。为什么?注意看 chanrecv 接收的参数是 block=true,并且这个 for-range 是一个死循环,除非 chanrecv2 返回值为 false,才有可能跳出循环,而 chanrecv2 在 block=true 场景下返回值为 false 的唯一原因只有:这个 chan 是 close 状态。
总结
golang 的 chan 使用非常简单,这些简单的语法糖背后其实都是对应了相应的函数实现,这个翻译由编译器来完成。深入理解这些函数的实现,对于彻底理解 chan 的使用和限制条件是必不可少的。深入理解原理,知其然知其所以然,你才能随心所欲地使用 golang。