現(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邀桑。