8

多资源select操作的实现模式

 3 years ago
source link: https://www.zenlife.tk/select-channel.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

多资源select操作的实现模式

2016-02-24

先看这样一个需求:Go语言中,对chs []<-chan struct{}进行select操作,如果数组里所有channel都不可读,就阻塞调用的goroutine。

似乎是没有什么直接的方式,比如这样是不行的:

for _, c := range chs {
    select {
        case <-c:
            succ = true
            break
        default:
    }
}
if !succ {
    os.yield()
}
问题出在最后的yield操作只是让出了CPU,但并不是将当前goroutine设置成阻塞状态。这个不同之处在于,前者只是丢到了READY队列的队尾,下一轮还是会运行,浪费CPU,而后者丢到了阻塞队列,除非满足了某些唤醒条件,它是不会再调度的。

在看云风的ltask的时候,发现它里面也要实现类似的模式,惊讶地发现它是直接调用coroutine.yield了,不禁想比较一下它为什么能这么做。发现ltask在调用select没有可用channel的时候,其实它就已经设置task的状态为阻塞了,跟Go不同,Go中没有暴露出将goroutine状态改为阻塞的方式。

还有一处发现是ltask中select的实现是逐个地对多个channel依次加锁,判断是否可用,释放锁。

ltask.select是支持操作多个channel,理论上跟Go应该是一样的。但这里跟Go实现方式不一样,引起了我的注意。

分析以后发现,ltask里面的写channel操作是不阻塞的,这样不会出现死锁的成环条件。不知道云风是碰巧设计的还是有意为之。总之若没有这项约束,这种实现就是隐藏较深的BUG。

为什么Go语言中不这么做?记得我在一次技术分享的时候提到过这个问题。我们看一下:

select {
    case c1<-1:
    case c2<-2:
}

select {
    case <-c2:
    case <-c1:
}
假设两个goroutine分别在运行上面的两个select,如果select的实现方式是依次对每一项channel加锁,判断是否可用,解锁,这样的流程会出现什么问题呢?我们假设goroutine1判断c1,发现不可写,同时goroutine2判断c2不可读。然后,goroutin1判断c2不可写,同时goroutine2判断c1不可读,于是—-死锁了。两个goroutine都进入了阻塞,并且再也没有机会唤醒!

select其实是一个整体,里面的资源不能独立对待。要么全部成功,要么失败,否则可能死锁。

所以在Go中的实现是,对select里面的资源全部加锁之后,才执行后面的操作。为了解决加锁时的死锁,Go是对channel的结构体按地址排序后,按顺序加锁的。select操作是一个比较有开销的实现。

我去网上查相关的资料,还看到有人问到select的实现,Dmitry Vyukov(Go调度器的实现者,大牛一位)回答,本来是可以把channel做成无锁的,但正是select的存在,让无锁的channel很难实现了。然后Rob Pike跟贴,说当年他还专门发过paper讲select的实现。

再推广一下,其实这并不仅仅是在Go语言中的一个模式,其实在很多其它地方都有涉及。

宽泛地说就是执行多个资源的select,如果满足其中一个,就能继续执行。如果一个都不满足,则将当前的执行单元阻塞。将来在这些资源之一就成可用时唤醒执行单元。

我猜你大概能想到的,操作系统的select/epoll调用,或者fork以后多个进行的accept操作,都具有类似的模式。

那么还不难想到另一个:惊群问题。也就是,被唤醒的执行单元做了一次无意义的唤醒,然后再做一轮抢资源,只有一个胜出。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK