如何把golang的Channel玩出async和await的feel

引言

如何優(yōu)雅的同步化異步代碼,一直以來都是各大編程語言致力于優(yōu)化的點,記得最早是C# 5.0加入了async/await來簡化TPL的多線程模型,后來Javascript的Promise也吸取這一語法糖,在ES 6中也加入了async和await.

那么,被大家一稱贊并發(fā)性能好、異步模型獨樹一幟的golang,能否也有async和await呢?

其實,這對于golang的CSM來說一點也不難!

核心代碼如下:

done := make(chan struct{})
go func() {
    // do work asynchronously here
    //
    close(done)
}()
<-done

是不是很簡單呢? go rountine負責async, channel的負責await, 簡直是完美!

但這個代碼看起來還是有點丑,而且這個go func(){}還沒有返回值,雖說可以通過閉包來接收返回值,但那個代碼就更難維護了。

Go Promise

代碼難看不要緊,只要Don't repeat yourself (DRY),封裝一下不就好了?

type WorkFunc func() (interface{}, error)

func NewPromise(workFunc WorkFunc) *Promise {
    promise := Promise{done: make(chan struct{})}
    go func() {
        defer close(promise.done)
        promise.res, promise.err = workFunc()
    }()
    return &promise
}

func (p *Promise) Done() (interface{}, error) {
    <-p.done
    return p.res, p.err
}

調(diào)用的代碼如下:

promise := NewPromise(func() (interface{}, error) {
    // do work asynchronously here
    //
    return res, err
})

// await
res, err := promise.Done()

是不是美觀了許多呢?

這個實現(xiàn)和Javascript的Promise的API是有很大差距,使用體驗上因為golang沒有泛型,也需要轉(zhuǎn)來轉(zhuǎn)去的,但為了不辜負Promise這個名字,怎么能沒有then呢?

type SuccessHandler func(interface{}) (interface{}, error)

type ErrorHandler func(error) interface{}

func (p *Promise) Then(successHandler SuccessHandler, errorHandler ErrorHandler) *Promise {
    newPromise := &Promise{done: make(chan struct{})}
    go func() {
        res, err := p.Done()
        defer close(newPromise.done)
        if err != nil {
            if errorHandler != nil {
                newPromise.res = errorHandler(err)
            } else {
                newPromise.err = err
            }
        } else {
            if successHandler != nil {
                newPromise.res, newPromise.err = successHandler(res)
            } else {
                newPromise.res = res
            }
        }
    }()

    return newPromise
}

有了then可以chain起來,是不是找到些Promise的感覺呢?

完整代碼請查看 promise.go

Actor

本來我的理解也就到些了,然后前段時間(說來也是一月有余了),看了Go并發(fā)設計模式之 Active Object這篇文章后, 發(fā)現(xiàn)如果有一個常駐協(xié)程在異步的處理任務,而且是FIFO的,那么這其實是相當于一個無鎖的設計,可以簡化對臨界資源的操作。

于是,我照著文章的思路,實現(xiàn)了下面的代碼:

// Creates a new actor
func NewActor(setActorOptionFuncs ...SetActorOptionFunc) *Actor {
    actor := &Actor{buffer: runtime.NumCPU(), quit: make(chan struct{}), wg: &sync.WaitGroup{}}
    for _, setOptionFunc := range setActorOptionFuncs {
        setOptionFunc(actor)
    }

    actor.queue = make(chan request, actor.buffer)

    actor.wg.Add(1)
    go actor.schedule()

    return actor
}

// The long live go routine to run.
func (actor *Actor) schedule() {
loop:
    for {
        select {
        case request := <-actor.queue:
            request.promise.res, request.promise.err = request.work()
            close(request.promise.done)
        case <-actor.quit:
            break loop
        }
    }
    actor.wg.Done()
}

// Do a work.
func (actor *Actor) Do(workFunc WorkFunc) *Promise {
    methodRequest := request{work: workFunc, promise: &Promise{
        done: make(chan struct{}),
    }}
    actor.queue <- methodRequest
    return methodRequest.promise
}

// Close actor
func (actor *Actor) Close() {
    close(actor.quit)
    actor.wg.Wait()
}

一個簡單的沒啥意義的純粹為了demo的測試用例如下:

func TestActorAsQueue(t *testing.T) {
    actor := NewActor()
    defer actor.Close()

    i := 0
    workFunc := func() (interface{}, error) {
        time.Sleep(1 * time.Second)
        i++
        return i, nil
    }

    promise := actor.Do(workFunc)
    promise2 := actor.Do(workFunc)

    res2, _ := promise2.Done()
    res1, _ := promise.Done()

    if res1 != 1 {
        t.Fail()
    }

    if res2 != 2 {
        t.Fail()
    }
}

完整代碼請查看 actor.go

總結(jié)

每個語言都有它的獨特之處,在我的理解中,玩轉(zhuǎn)golang的CSM模型,channel一定要用的6。

于是,我創(chuàng)建了Channelx這個repo, 包含了對channel常用場景的封裝,歡迎大家審閱,喜歡的就點個star。

此系列其它文章:

如何用Golang的channel實現(xiàn)消息的批量處理

如何把Golang的channel用的如nodejs的stream一樣絲滑

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容