<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>。
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之间的关系
<blockquote class="layui-elem-quote" style="width: 100%;overflow:hidden">
作者: weixin_42282999
链接: https://blog.csdn.net/i6448038/article/details/88931923
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
</blockquote>
到此这篇关于“go select原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!
您可能感兴趣的文章:
go语言并发编程
图解 Go 并发编程
Golang号称高并发,但高并发时性能不高解决办法
想系统学习GO语言(Golang
golang学习笔记(二)—— 深入golang中的协程
go html提取纯文本_Go 语言高性能编程
Golang并发:并发协程的优雅退出
浅谈 Go 语言 select 的实现原理
22Go常见的并发模式和并发模型
Golang 并行运算以及time/sleep.go