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

引言

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

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

其實(shí),這對(duì)于golang的CSM來(lái)說(shuō)一點(diǎn)也不難剧防!

核心代碼如下:

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

是不是很簡(jiǎn)單呢植锉? go rountine負(fù)責(zé)async, channel的負(fù)責(zé)await, 簡(jiǎn)直是完美峭拘!

但這個(gè)代碼看起來(lái)還是有點(diǎn)丑俊庇,而且這個(gè)go func(){}還沒(méi)有返回值狮暑,雖說(shuō)可以通過(guò)閉包來(lái)接收返回值,但那個(gè)代碼就更難維護(hù)了辉饱。

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()

是不是美觀了許多呢缔逛?

這個(gè)實(shí)現(xiàn)和Javascript的Promise的API是有很大差距,使用體驗(yàn)上因?yàn)間olang沒(méi)有泛型姓惑,也需要轉(zhuǎn)來(lái)轉(zhuǎn)去的褐奴,但為了不辜負(fù)Promise這個(gè)名字,怎么能沒(méi)有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起來(lái)敦冬,是不是找到些Promise的感覺(jué)呢?

完整代碼請(qǐng)查看 promise.go

Actor

本來(lái)我的理解也就到些了唯沮,然后前段時(shí)間(說(shuō)來(lái)也是一月有余了)脖旱,看了Go并發(fā)設(shè)計(jì)模式之 Active Object這篇文章后, 發(fā)現(xiàn)如果有一個(gè)常駐協(xié)程在異步的處理任務(wù)介蛉,而且是FIFO的萌庆,那么這其實(shí)是相當(dāng)于一個(gè)無(wú)鎖的設(shè)計(jì),可以簡(jiǎn)化對(duì)臨界資源的操作币旧。

于是踊兜,我照著文章的思路,實(shí)現(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()
}

一個(gè)簡(jiǎn)單的沒(méi)啥意義的純粹為了demo的測(cè)試用例如下:

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()
    }
}

完整代碼請(qǐng)查看 actor.go

總結(jié)

每個(gè)語(yǔ)言都有它的獨(dú)特之處佳恬,在我的理解中捏境,玩轉(zhuǎn)golang的CSM模型,channel一定要用的6毁葱。

于是垫言,我創(chuàng)建了Channelx這個(gè)repo, 包含了對(duì)channel常用場(chǎng)景的封裝,歡迎大家審閱倾剿,喜歡的就點(diǎn)個(gè)star筷频。

此系列其它文章:

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市前痘,隨后出現(xiàn)的幾起案子凛捏,更是在濱河造成了極大的恐慌,老刑警劉巖芹缔,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯癣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡最欠,警方通過(guò)查閱死者的電腦和手機(jī)示罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門惩猫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蚜点,你說(shuō)我怎么就攤上這事轧房。” “怎么了绍绘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵奶镶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我陪拘,道長(zhǎng)实辑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任藻丢,我火速辦了婚禮,結(jié)果婚禮上摄乒,老公的妹妹穿的比我還像新娘悠反。我一直安慰自己,他們只是感情好馍佑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布斋否。 她就那樣靜靜地躺著,像睡著了一般拭荤。 火紅的嫁衣襯著肌膚如雪茵臭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天舅世,我揣著相機(jī)與錄音旦委,去河邊找鬼。 笑死雏亚,一個(gè)胖子當(dāng)著我的面吹牛缨硝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罢低,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼查辩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了网持?” 一聲冷哼從身側(cè)響起宜岛,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎功舀,沒(méi)想到半個(gè)月后萍倡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辟汰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年遣铝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了佑刷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酿炸,死狀恐怖瘫絮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情填硕,我是刑警寧澤麦萤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扁眯,受9級(jí)特大地震影響壮莹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姻檀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一命满、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绣版,春花似錦胶台、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缩麸,卻和暖如春铸磅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杭朱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工阅仔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弧械。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓霎槐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梦谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丘跌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容