这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战
龙飞介绍完scheduler后,袁小白感觉是懂非懂。 说懂呢,因为大概了解到scheduler是如何将edge进行构建的,是如何拆解的,等等。 但要说到底是如何工作的,却又理不清,道不明。 这不,pipe就是其中之一。 在好奇心驱使下,袁小白开始分析起了pipe。
pipe使用场景之Receive
func TestPipe(t *testing.T) {
t.Parallel()
runCh := make(chan struct{})
f := func(ctx context.Context) (interface{}, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-runCh:
return "res0", nil
}
}
waitSignal := make(chan struct{}, 10)
signalled := 0
signal := func() {
signalled++
waitSignal <- struct{}{}
}
p, start := NewWithFunction(f)
p.OnSendCompletion = signal
go start()
require.Equal(t, false, p.Receiver.Receive())
st := p.Receiver.Status()
require.Equal(t, st.Completed, false)
require.Equal(t, st.Canceled, false)
require.Nil(t, st.Value)
require.Equal(t, signalled, 0)
close(runCh)
<-waitSignal
p.Receiver.Receive()
st = p.Receiver.Status()
require.Equal(t, st.Completed, true)
require.Equal(t, st.Canceled, false)
require.NoError(t, st.Err)
require.Equal(t, st.Value.(string), "res0")
}
可以看出如果我想通过pipe执行一个有阻塞的功能,如:
f := func(ctx context.Context) (interface{}, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-runCh:
return "res0", nil
}
}
这种场景很常见,比如HTTP网络请求,要等到runCh收到消息才能返回结果。
我们在使用NewWithFunction创建pipe的同时,还返回了一个start函数,同时我们可以设置pipe的OnSendCompletion事件,这样我们就可以在send动作完成的时候,做我们想做的事了,这里想做的事是signal,让计数器signalled累加一,然后写入waitSignal通道信息。
可以理解为当send操作完成时,通过waitSignal信道,进行通知。
pipe执行go start(),Goroutine基本单元创建。
这时去检查pipe的Receiver的Status时,发现,并没有真正执行:
require.Equal(t, false, p.Receiver.Receive())
st := p.Receiver.Status()
require.Equal(t, st.Completed, false)
require.Equal(t, st.Canceled, false)
require.Nil(t, st.Value)
require.Equal(t, signalled, 0)
关闭runCh通道,触发了用户函数最终结果的返回。
但此时仍需等待Goroutine go start()真正的运行,用<-waitSignal进行等待。
等到pipe的OnSendCompletion被调用时,也就意味着收到了waitSignal信道的消息,可以继续执行下去了。
再来看对应的状态,也就发生了期待中的变化:
p.Receiver.Receive()
st = p.Receiver.Status()
require.Equal(t, st.Completed, true)
require.Equal(t, st.Canceled, false)
require.NoError(t, st.Err)
require.Equal(t, st.Value.(string), "res0")
pipe使用场景之Cancel
和Receiver正常工作不同的是,Cancel取消了请求:
p.Receiver.Cancel()
<-waitSignal
我们期待的结果,请求真正的被取消了:
p.Receiver.Receive()
st = p.Receiver.Status()
require.Equal(t, st.Completed, true)
require.Equal(t, st.Canceled, true)
require.Error(t, st.Err)
require.Equal(t, st.Err, context.Canceled)
通过管道pipe的使用场景,我们可以了解到设计初衷。 希望像linux上的管道一样,在一端输入请求,在另一端接收结果。 这里的请求可以是一个函数-f,并且可以是异步操作,在请求真正被发送的时候,我们有回调函数,可以让使用者接着处理或触发进一步的操作。 最后通过Receiver的状态,接收返回状态和返回结果,这个结果是真正的用户请求的结果。
再试想一下,如果多个管道连接在一起会是什么样? 比如总共有三个管道,彼此互相联接,前一个管理的结果想要传递给后一个时,可以让后一个管理持有前一个管道的Receiver,并在前一个管道的OnSendCompletion事件中注册触发事件,那这样是不是就可以把依赖顺序设定好,并且接计划执行了?!
那这个神奇的pipe到底是怎么实现的呢?
好奇心又让袁小白忍不住接着往下看了。
原来pipe是由这些组件组成的,那他们又是怎么互相协作,才能变出这么神奇的魔术的呢?