jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

深入Golang之select

作者:jasper | 分类:Golang | 标签:   | 阅读 128 次 | 发布:2017-10-21 6:52 p.m.

接着上一篇的channel来说,channel就像一个管道,而我们在利用Golang的goroutines来编程的时候,对于多个goroutines(最常见的用time.Tick来控制时间的)的情况,我们一般需要用select语句来接收channel中的数据。本篇就来看看select的内部实现。

数据结构

type hselect struct {
    tcase     uint16   // scase[]的总数
    ncase     uint16   // 当前填充的scase[]数量
    pollorder *uint16  // case的poll次序
    lockorder *uint16  // channel锁住的次序
    scase     [1]scase // 每个case会在结构体里有一个scase,顺序是按照出现的次序
    }

type scase struct {
    elem        unsafe.Pointer // 数据的指针
    c           *hchan         // chan
    pc          uintptr        // 
    kind        uint16
    receivedp   *bool
    releasetime int64
}

每个select都对应一个hselect结构体,在hselect数据结构中有个scase数组,会记录下每个case,在scase中包含了hchan,然后pollorder数组将元素随机排列,这样来实现scase的乱序。所以select和switch中的case在执行顺序上是不一样的,switch中的case是一条一条的顺序执行,而select中的case是随机执行的。

底层实现

select-case的channel操作实现

select-case中的chan的操作会被编译为if-else语句,例如:

select {
case c <- x:
    ... foo
default:
    ... bar
}

会被编译为:

if selectnbsend(c, v) {
    ... foo
} else {
    ... bar
}

其中selectnbsend的实现为:

func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
    return chansend(c, elem, false, getcallerpc(unsafe.Pointer(&c)))
}

而:

select {
case v = <-c
    ... foo
default:
    ... bar
}

会被编译为:

if selectnbrecv(&v, c) {
    ... foo
} else {
    ... bar
}

其中selectnbrecv的实现为:

func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
    selected, _ = chanrecv(c, elem, false)
    return
}

最后:

select {
case v, ok = <-c:
    ... foo
default:
    ... bar
}

会被编译为:

if c != nil && selectnbrecv2(&v, &ok, c) {
    ... foo
} else {
    ... bar
}

对应的selectnbrecv2函数为:

func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
    selected, *received = chanrecv(c, elem, false)
    return
}

OK,上面这些函数都是调用的我上一篇channel里面提到过,这里就不多赘述,下面我们着重来看一下select的逻辑实现。

select的底层实现

对于select-case语句本身来说,其中的逻辑都是在函数selectgo中。下面我们来看一下具体实现。

针对select中的case生成排序:

pollslice := slice{unsafe.Pointer(sel.pollorder), int(sel.ncase), int(sel.ncase)}
pollorder := *(*[]uint16)(unsafe.Pointer(&pollslice))
for i := 1; i < int(sel.ncase); i++ {
    j := fastrandn(uint32(i + 1))
    pollorder[i] = pollorder[j]
    pollorder[j] = uint16(i)
}

然后生成lockorder,也即上面所说的channel锁住的次序:

lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)}
    lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice))
    for i := 0; i < int(sel.ncase); i++ {
        j := i
        c := scases[pollorder[i]].c
        for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() {
            k := (j - 1) / 2
            lockorder[j] = lockorder[k]
            j = k
        }
        lockorder[j] = pollorder[i]
    }

接下来就是select监听所有channel的逻辑了,主要由代码段loop来控制,会不断循环遍历所有的case,然后根据case的类型来决定执行逻辑:

for i := 0; i < int(sel.ncase); i++ {
        casi = int(pollorder[i])
        cas = &scases[casi]
        c = cas.c

        switch cas.kind {
            case caseNil:
             ....
            case caseRecv:
             ....
            case caseSend:
             ....
            case caseDefault:
             ....  
        }
}

其中caseRecvcaseSend时,会从channel中dequeue出数据,然后执行recv或是send逻辑。这部分的逻辑同样在上一篇中的recvsend函数中,具体的就不赘述。


转载请注明出处:http://www.opscoder.info/golang_select.html

【上一篇】 深入Golang之channel
【下一篇】 深入Golang之goroutine
其他分类: