AI智能
改变未来

Go中select语句的底层原理

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

实现原理:
  • golang实现select的时候,实际上为每一个case语句定义了一个数据结构,select语句块执行的时候,实际上可以类比成对一个case数组处理的代码块(或者函数),然后程序流程转到选中的case块。
case数据结构
  • 源码包src/runtime/select.go:scase定义了表示case语句的数据结构:
type scase struct {c           *hchan         // chankind        uint16elem        unsafe.Pointer // data element}

scase.c表示当前case语句操作的chan指针,这也表明一个case只能监听一个chan。
scase.kind表示当前的chan是可读还是可写channel或者是default。三种类型分别由常量定义:

  • caseRecv:case语句中尝试读取scase.c中的数据;
  • caseSend:case语句中尝试向scase.c中写入数据;
  • caseDefault: default语句
    scase.elem表示缓冲区地址,跟据scase.kind不同,有不同的用途:
  • scase.kind == caseRecv : scase.elem表示读出channel的数据存放地址;
  • scase.kind == caseSend : scase.elem表示将要写入channel的数据存放地址;
select实现逻辑

源码包src/runtime/select.go:selectgo()定义了select选择case的函数:

// selectgo implements the select statement.//// *sel is on the current goroutine\'s stack (regardless of any// escaping in selectgo).//// selectgo returns the index of the chosen scase, which matches the// ordinal position of its respective select{recv,send,default} call.func selectgo(sel *hselect) int {<br>}

其中数据结构hselect如下:

// Select statement header.// Known to compiler.// Changes here must also be made in src/cmd/internal/gc/select.go\'s selecttype.type hselect struct {tcase     uint16   // total count of scase[]    ncase     uint16   // currently filled scase[]pollorder *uint16  // case poll orderlockorder *uint16  // channel lock orderscase     [1]scase // one per case (in order of appearance)}

hselect.tcase存的是scase总数。
hselect.pollorder是保存scase的随机后的序列。以达到随机检测case的目的。
hselect.lockorder是保存的channel地址。所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的。
selectgo返回int,表示选中的scase,也就是ready的channel index。

该函数执行逻辑大致如下:

  1. 锁定scase语句中所有的channel
  2. 按照随机顺序检测scase中的channel是否ready  
    2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index) 
    2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index)
    2.3 所有case都未ready,则解锁所有的channel,然后返回(default index)
  3. 所有case都未ready,且没有default语句 
    3.1 将当前协程加入到所有channel的等待队列  
    3.2 当将协程转入阻塞,等待被唤醒
  4. 唤醒后返回channel对应的case index
    4.1 如果是读操作,解锁所有的channel,然后返回(case index) 
    4.2 如果是写操作,解锁所有的channel,然后返回(case index)
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Go中select语句的底层原理