Go 語言中使用 Future/Promise 編程模式

現(xiàn)如今,應(yīng)用執(zhí)行時(shí)最普遍存在的瓶頸就是網(wǎng)絡(luò)請(qǐng)求了。網(wǎng)絡(luò)請(qǐng)求只要幾毫秒们陆,但是等到返回卻要百倍的時(shí)間寒瓦。所以,如果你執(zhí)行多個(gè)網(wǎng)絡(luò)請(qǐng)求坪仇,讓他們都并行執(zhí)行就是減少延遲最好的選擇了杂腰。Future/Promise 就是實(shí)現(xiàn)這一目的的手段之一。

一個(gè) Future 就是說“將來”你需要某些東西(一般就是一個(gè)網(wǎng)絡(luò)請(qǐng)求的結(jié)果)椅文,但是你現(xiàn)在就要發(fā)起這樣的請(qǐng)求喂很,并且這個(gè)請(qǐng)求會(huì)異步執(zhí)行〗源蹋或者換一個(gè)說法少辣,你需要在后臺(tái)執(zhí)行一個(gè)異步請(qǐng)求。

Future/Promise 模式在多種語言都有對(duì)應(yīng)的實(shí)現(xiàn)羡蛾。最典型的 C++11 標(biāo)準(zhǔn)庫中就提供了 future/promise毒坛,另外,再比如 ES2015 就有 Promise 和 async-await林说,Scala 也內(nèi)置了 Future。

最后屯伞,雖然 Go 語言沒有提供 future/promise 原語腿箩,但是我們可以使用 goroutine 和 channel 來實(shí)現(xiàn)類似的功能。下面給出一個(gè)簡單的實(shí)現(xiàn)劣摇。

// RequestFuture, http request promise.
func RequestFuture(url string) <-chan []byte {
    c := make(chan []byte, 1)
    go func() {
        var body []byte
        defer func() {
            c <- body
        }()

        res, err := http.Get(url)
        if err != nil {
            return
        }
        defer res.Body.Close()

        body, _ = ioutil.ReadAll(res.Body)
    }()

    return c
}

func main() {
    future := RequestFuture("https://api.github.com/users/octocat/orgs")
    body := <-future
    log.Printf("reponse length: %d", len(body))
}

RequestFuture 方法立刻返回一個(gè)channel珠移,這個(gè)時(shí)候 http 請(qǐng)求還在一個(gè) goroutine 后臺(tái)異步運(yùn)行。main 方法可以繼續(xù)執(zhí)行其他的代碼末融,比如觸發(fā)其他的 Future 等钧惧。當(dāng)需要結(jié)果的時(shí)候,我們需要從 channel 里讀取結(jié)果勾习。如果 http 請(qǐng)求還沒有返回的話就會(huì)阻塞當(dāng)前的 goroutine浓瞪,直到結(jié)果返回。

然而巧婶,以上的方法還有一點(diǎn)局限乾颁。錯(cuò)誤無法返回。在上面的例子里艺栈,如果 http 請(qǐng)求出現(xiàn)錯(cuò)誤的話英岭,body 的值會(huì)是 nil/empty。但是湿右,由于 channel 只能返回一個(gè)值诅妹,你需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的 struct 來包裝兩個(gè)返回的結(jié)果。

修改以后的結(jié)果:

// RequestFutureV2 return value and error
func RequestFutureV2(url string) func() ([]byte, error) {
    var body []byte
    var err error

    c := make(chan struct{}, 1)
    go func() {
        defer close(c)

        var res *http.Response
        res, err = http.Get(url)
        if err != nil {
            return
        }

        defer res.Body.Close()
        body, err = ioutil.ReadAll(res.Body)
    }()

    return func() ([]byte, error) {
        <-c
        return body, err
    }
}

這個(gè)方法返回了兩個(gè)結(jié)果毅人,解決了第一個(gè)方法的局限性問題吭狡。使用的時(shí)候是這樣的:

func main() {
    futureV2 := RequestFutureV2("https://api.github.com/users/octocat/orgs")

    // not block
    log.Printf("V2 is this locked again")

    bodyV2, err := futureV2() // block
    if err == nil {
        log.Printf("V2 response length %d\n", len(bodyV2))
    } else {
        log.Printf("V2 error is %v\n", err)
    }
}

上面的修改帶來的好處就是 futureV2() 方法的調(diào)用可以是多次的尖殃。并且都可以返回同樣的結(jié)果。

但是赵刑,如果你想用這個(gè)方法實(shí)現(xiàn)很多不同的異步功能分衫,你需要寫很多的額外的代碼。我們可以寫一個(gè)util 方法來克服這個(gè)困難般此。

// Future boilerplate method
func Future(f func() (interface{}, error)) func() (interface{}, error) {
    var result interface{}
    var err error

    c := make(chan struct{}, 1)
    go func() {
        defer close(c)
        result, err = f()
    }()

    return func() (interface{}, error) {
        <-c
        return result, err
    }
}

調(diào)用 Future 方法的時(shí)候會(huì)執(zhí)行房里的很多 channel 方面的小技巧蚪战。為了能夠達(dá)到通用的目的,這里可以使用一個(gè)從 []byte->interface{}->[]byte 的類型轉(zhuǎn)換铐懊。如果出現(xiàn)錯(cuò)的話會(huì)引發(fā)一個(gè)運(yùn)行時(shí)的 panic邀桑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市科乎,隨后出現(xiàn)的幾起案子壁畸,更是在濱河造成了極大的恐慌,老刑警劉巖茅茂,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捏萍,死亡現(xiàn)場離奇詭異,居然都是意外死亡空闲,警方通過查閱死者的電腦和手機(jī)令杈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碴倾,“玉大人逗噩,你說我怎么就攤上這事〉疲” “怎么了异雁?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長僧须。 經(jīng)常有香客問我纲刀,道長,這世上最難降的妖魔是什么担平? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任柑蛇,我火速辦了婚禮,結(jié)果婚禮上驱闷,老公的妹妹穿的比我還像新娘耻台。我一直安慰自己,他們只是感情好空另,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布盆耽。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摄杂。 梳的紋絲不亂的頭發(fā)上坝咐,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音析恢,去河邊找鬼墨坚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛映挂,可吹牛的內(nèi)容都是我干的泽篮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼柑船,長吁一口氣:“原來是場噩夢啊……” “哼帽撑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鞍时,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤亏拉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逆巍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體及塘,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年锐极,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磷蛹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡溪烤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庇勃,到底是詐尸還是另有隱情檬嘀,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布责嚷,位于F島的核電站鸳兽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏罕拂。R本人自食惡果不足惜揍异,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爆班。 院中可真熱鬧衷掷,春花似錦、人聲如沸柿菩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至懦胞,卻和暖如春替久,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躏尉。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工蚯根, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胀糜。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓颅拦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僚纷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矩距,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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