요즘 애플리케이션 실행에서 가장 흔한 병목 현상은 네트워크 요청입니다. 네트워크 요청은 몇 밀리초밖에 걸리지 않지만 응답을 기다리는 데는 100배 더 오래 걸립니다. 따라서 여러 네트워크 요청을 수행하는 경우 모든 요청을 병렬로 실행하는 것이 대기 시간을 줄이는 가장 좋은 옵션입니다. Future/Promise는 이러한 목적을 달성하기 위한 수단 중 하나입니다.
미래는 "미래에"(일반적으로 네트워크 요청의 결과) 무언가가 필요하지만 지금 그러한 요청을 시작해야 하며 이 요청은 비동기적으로 실행된다는 것을 의미합니다. 또는 다르게 말하면 백그라운드에서 비동기 요청을 수행해야 합니다.
Future/Promise 패턴에는 여러 언어로 해당 구현이 있습니다. 예를 들어 ES2015에는 Promise와 async-await가 있고 Scala에는 Future가 내장되어 있으며 마지막으로 Golang에는 유사한 기능을 달성하기 위한 고루틴과 채널이 있습니다. 간단한 구현은 아래와 같습니다.
//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
메소드는 채널을 반환합니다. 이때 http 요청은 여전히 goroutine 백그라운드에서 비동기적으로 실행됩니다. 기본 메서드는 다른 Future
트리거 등의 다른 코드를 계속해서 실행할 수 있습니다. 결과가 필요할 때 채널에서 결과를 읽어야 합니다. http 요청이 반환되지 않은 경우 결과가 반환될 때까지 현재 고루틴이 차단됩니다. RequestFuture
方法理科返回一个channel,这个时候http请求还在一个goroutine后台异步运行。main方法可以继续执行其他的代码,比如触发其他的Future
等。当需要结果的时候,我们需要从channel里读取结果。如果http请求还没有返回的话就会阻塞当前的goroutine,知道结果返回。
然而,以上的方法还有一点局限。错误无法返回。在上面的例子里,如果http请求出现错误的话,body的值会是nil/empty。但是,由于channel只能返回一个值,你需要创建一个单独的struct来包装两个返回的结果。
修改以后的结果:
// 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 } }
这个方法返回了两个结果,解决了第一个方法的局限性问题。使用的时候是这样的:
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()
方法的调用可以是多次的。并且都可以返回同样的结果。
但是,如果你想用这个方法实现很多不同的异步功能,你需要写很多的额外的代码。我们可以写一个util方法来克服这个困难。
// 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 } }
调用Future
方法的时候会执行房里的很多channel方面的小技巧。为了能够达到通用的目的有一个从[]buyte
->interface{}
->[]byte
그러나 위의 방법에는 여전히 몇 가지 제한 사항이 있습니다. 오류를 반환할 수 없습니다. 위의 예에서 http 요청에 오류가 발생하면 body 값은 nil/empty가 됩니다. 그러나 채널은 하나의 값만 반환할 수 있으므로 반환된 두 결과를 래핑하려면 별도의 구조체를 만들어야 합니다. 수정 후 결과: