原文:http://labs.strava.com/blog/futures-in-golang/
我并没有打算逐字逐句的翻译,我觉得太死板, 所以我把核心大意说一下就好,如果您希望逐字逐句斟酌, 请看原文链接!
通常网络程序中, 大量时间花费在请求和响应上, 如果需要发送的请求比较多, 若以同步方式,则必然是发送请求,等待响应;
然后再发下一个请求并等待结果返回, 这样依次处理,其效率可想而知, 比较低下!若以异步方式,则可将所有请求并行发出,
而每一个请求的响应则以回调方式异步处理,这样效率自然是非常高,但是异步回调方式不直观,并碎片化业务代码!所以比较好
的方案就是: 代码形式是同步的,执行实质是异步的,当属:Futures/Promises, 其它语言早已有之。
见go代码:
package main
import (
"io/ioutil"
"log"
"net/http"
)
func RequestFuture(url string) <-chan []byte {
c := make(chan []byte, 1)
go func() {
var body []byte
defer func() {
c <- body
}()
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body) //同步阻塞调用,直到响应返回。
}()
return c
}
func main() {
future := RequestFuture("http://labs.strava.com")
// do many other things, maybe create other futures
body := <-future
log.Printf("response length: %d", len(body))
}
以上代码少而直观,不需多言,其缺点为:出错时channel只会返加nil/empty, 调用者拿不掉错误信息, 所以需改进。
见go代码:
unc RequestFutureFunction(url string) func() ([]byte, error) {
var body []byte
var err error
c := make(chan struct{}, 1)
go func() {
defer close(c) //1. 响应拿到了,并闭c.
var resp *http.Response
resp, err = http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)//2.同步阻塞调用,直到响应返回。
}()
return func() ([]byte, error) {
<-c //3.因为1处关闭了c,则此处不再同步阻塞等待,即响应已拿到,所以向下执行.
return body, err
}
}
func main() {
future := RequestFutureFunction("http://strava.com")
// do many other things, maybe create other futures
body, err := future()
log.Printf("response length: %d", len(body))
log.Printf("request error: %v", err)
}
以上代码主要改进:调用者可以拿到错误信息,再者以上代码返回的不再是channel而是一个function, 可以多次调用,即多次读取响应
而第一个版本,只能从channel中读取一次响应信息。以上代码可以工作得不错, 但是用起来麻烦且不通用, 所以要抽象一下。
见go代码:
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
}
}
func main() {
url := "http://labs.strava.com"
future := Future(func() (interface{}, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
})
// do many other things
b, err := future()
body, _ := b.([]byte)
log.Printf("response length: %d", len(body))
log.Printf("request error: %v", err)
}
以上代码为抽象封装后的代码, 使用时, 需调用者传入同步执行的函数,而以上代码对这个同步函数进行异步封装,成为Futures/Promises.
其实代码改进到此处,已经非常明晰好用,只是因为golang不支持C++那样的模板机制, 所以存在:[]byte
-> interface{}
-> []byte
.
这样的数据类型转换,所以在使用时, 如果数据类型不匹配, 则类型转换很可能失败引发运行时异常, 所以此篇
文章作者给出一个方案:代码生成器,实现代码我没看, 估计是不同的类型实现,用id表记,再提供一个工厂方法,
这样就可以以id获取对应数据类型的Futures/Promises代码实现,具体如何,您有时间可以深入研究一下!
注意: 此文章只是我个人笔记, 如有错漏,请一定指正, 共同学习, 我的邮箱: htyu_0203_39@sina.com