教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 go select原理

go select原理

发布时间:2022-01-31   编辑:jiaochengji.com
教程集为您提供go select原理等资源,欢迎您收藏本站,我们将为您提供最新的go select原理资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg>

Go 的<code>select语句</code>是一种仅能用于<code>channl发送和接收消息</code>的<code>专用语句</code>,此语句<code>运行期间</code>是<code>阻塞的</code>;当select中<code>没有case语句</code>的时候,会阻塞<code>当前的groutine</code>。所以,有人也会说select是用来<code>阻塞监听goroutine</code>的。
还有人说:select是Golang在语言层面提供的<code>I/O多路复用</code>的机制,其专门用来检测<code>多个channel</code>是否准备完毕:<code>可读或可写</code>。

以上说法都<code>正确</code>。

<h2>1、I/O多路复用</h2> <h3>(1)普通多线程(或进程)I/O</h3>

每来一个进程,都会建立连接,然后阻塞,直到接收到数据返回响应。
普通这种方式的缺点其实很明显:系统需要<code>创建和维护</code>额外的<code>线程或进程</code>。因为大多数时候,<code>大部分</code>阻塞的线程或进程是处于<code>等待状态</code>,只有<code>少部分</code>会接收并处理响应,而其余的都在等待。系统为此还需要多做很多额外的线程或者进程的管理工作。
为了解决这些多余的线程或者进程,于是有了"<code>I/O多路复用</code>"

<h3>
(2)I/O多路复用</h3>


每个线程或者进程都先到图中”装置“中注册,然后阻塞,然后只有一个线程在”运输“,当注册的线程或者进程准备好数据后,”装置“会根据注册的信息得到相应的数据。从始至终kernel只会使用图中这个黄黄的线程,无需再对额外的线程或者进程进行管理,提升了效率。

<h2>
2、select组成结构</h2>

常见代码:

<pre><code class="lang-go hljs"> ch1 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> ch2 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> ch1 <span class="token operator"><-</span> <span class="token number">1</span> <span class="token keyword">select</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> e1 <span class="token operator">:=</span> <span class="token operator"><-</span>ch1<span class="token punctuation">:</span> <span class="token comment">//如果ch1通道成功读取数据,则执行该case处理语句</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"1th case is selected. e1=%v"</span><span class="token punctuation">,</span> e1<span class="token punctuation">)</span> <span class="token keyword">case</span> e2 <span class="token operator">:=</span> <span class="token operator"><-</span>ch2<span class="token punctuation">:</span> <span class="token comment">//如果ch2通道成功读取数据,则执行该case处理语句</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"2th case is selected. e2=%v"</span><span class="token punctuation">,</span> e2<span class="token punctuation">)</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token comment">//如果上面case都没有成功,则进入default处理流程</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"default!."</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

select这个语句底层实现实际上主要由<code>两部分</code>组成:<code>case语句</code>和<code>执行函数</code>。
每个case语句,单独抽象出以下结构体:

<pre><code class="lang-go hljs"><span class="token keyword">type</span> scase <span class="token keyword">struct</span> <span class="token punctuation">{</span> c <span class="token operator">*</span>hchan <span class="token comment">// chan</span> elem unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// 读或者写的缓冲区地址</span> kind <span class="token builtin">uint16</span> <span class="token comment">//case语句的类型,是default、传值写数据(channel <-) 还是 取值读数据(<- channel)</span> pc <span class="token builtin">uintptr</span> <span class="token comment">// race pc (for race detector / msan)</span> releasetime <span class="token builtin">int64</span> <span class="token punctuation">}</span> </code></pre>

结构体可以用下图表示:

其中比较关键的是:<code>hchan</code>,它是<code>channel的指针</code>,用来存放<code>等待的协程</code>。
在一个<code>select中</code>,<code>所有的case语句</code>会构成一个<code>scase结构体</code>的<code>数组</code>。
然后执行select语句实际上就是调用<code>func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)</code>函数。

<blockquote>

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int,
bool)函数参数:

<ul><li>cas0 为上文提到的case语句抽象出的结构体scase数组的第一个元素地址</li><li>order0为一个两倍cas0数组长度的buffer,保存scase随机序列pollorder和scase中channel地址序列lockorder。</li><li>nncases表示scase数组的长度</li><li>selectgo返回<code>所选scase的索引(</code>该索引与其各自的select {recv,send,default}调用的序号位置相匹配)。此外,如果选择的scase是接收操作(recv),则返回<code>是否接收到值</code>。</li></ul></blockquote>

谁负责调用func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函数呢?
在<code>/reflect/value.go</code>中有个func rselect([]runtimeSelect) (chosen int, recvOK bool)函数,此函数的实现在<code>/runtime/select.go</code>文件中的func reflect_rselect(cases []runtimeSelect) (int, bool)函数中:

<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token function">reflect_rselect</span><span class="token punctuation">(</span>cases <span class="token punctuation">[</span><span class="token punctuation">]</span>runtimeSelect<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">//如果cases语句为空,则阻塞当前groutine</span> <span class="token keyword">if</span> <span class="token function">len</span><span class="token punctuation">(</span>cases<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">block</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">//实例化case的结构体</span> sel <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>scase<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>cases<span class="token punctuation">)</span><span class="token punctuation">)</span> order <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">uint16</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token operator">*</span><span class="token function">len</span><span class="token punctuation">(</span>cases<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> cases <span class="token punctuation">{</span> rc <span class="token operator">:=</span> <span class="token operator">&</span>cases<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token keyword">switch</span> rc<span class="token punctuation">.</span>dir <span class="token punctuation">{</span> <span class="token keyword">case</span> selectDefault<span class="token punctuation">:</span> sel<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> scase<span class="token punctuation">{</span>kind<span class="token punctuation">:</span> caseDefault<span class="token punctuation">}</span> <span class="token keyword">case</span> selectSend<span class="token punctuation">:</span> sel<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> scase<span class="token punctuation">{</span>kind<span class="token punctuation">:</span> caseSend<span class="token punctuation">,</span> c<span class="token punctuation">:</span> rc<span class="token punctuation">.</span>ch<span class="token punctuation">,</span> elem<span class="token punctuation">:</span> rc<span class="token punctuation">.</span>val<span class="token punctuation">}</span> <span class="token keyword">case</span> selectRecv<span class="token punctuation">:</span> sel<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> scase<span class="token punctuation">{</span>kind<span class="token punctuation">:</span> caseRecv<span class="token punctuation">,</span> c<span class="token punctuation">:</span> rc<span class="token punctuation">.</span>ch<span class="token punctuation">,</span> elem<span class="token punctuation">:</span> rc<span class="token punctuation">.</span>val<span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> raceenabled <span class="token operator">||</span> msanenabled <span class="token punctuation">{</span> <span class="token function">selectsetpc</span><span class="token punctuation">(</span><span class="token operator">&</span>sel<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token function">selectgo</span><span class="token punctuation">(</span><span class="token operator">&</span>sel<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">&</span>order<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>cases<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre> <blockquote>

以上这三个函数的调用栈按顺序如下:

<ul><li>func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)</li><li>func rselect([]runtimeSelect) (chosen int, recvOK bool) func</li><li>selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)</li></ul></blockquote>


前两个函数<code>Select</code>和<code>rselect</code>都是做了<code>简单的初始化参数</code>,调用下一个函数的操作。select真正的核心功能,是在最后一个函数<code>func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)</code>中实现的。

<h3>
selectgo函数做了什么</h3>

打乱传入的case结构体顺序

锁住其中的所有的channel

遍历所有的channel,查看其是否可读或者可写


如果其中的channel可读或者可写,则解锁所有channel,并返回对应的channel数据


假如没有channel可读或者可写,但是有default语句,则同上:返回default语句对应的scase并解锁所有的channel。

假如既没有channel可读或者可写,也没有default语句,则将当前运行的<code>groutine阻塞</code>,并加入到<code>当前所有channel</code>的<code>等待队列</code>中去。

然后解锁所有channel,等待被唤醒。
此时如果有个channel可读或者可写ready了,则唤醒,并再次加锁所有channel,

遍历所有channel找到那个对应的channel和G,唤醒G,并将没有成功的G从所有channel的等待队列中移除。

如果对应的scase值不为空,则返回需要的值,并解锁所有channel

如果对应的scase为空,则循环此过程。
select和channel之间的关系

到此这篇关于“go select原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
go语言并发编程
图解 Go 并发编程
Golang号称高并发,但高并发时性能不高解决办法
想系统学习GO语言(Golang
golang学习笔记(二)—— 深入golang中的协程
go html提取纯文本_Go 语言高性能编程
Golang并发:并发协程的优雅退出
浅谈 Go 语言 select 的实现原理
22Go常见的并发模式和并发模型
Golang 并行运算以及time/sleep.go

[关闭]
~ ~