future
和promise
在很多语言中都很常用,它们通常被用来处理异步请求。
在下面的例子里,sumSquares
的2个参数都需要通过异步请求获得,这里用了2个channel
来处理异步请求。虽然每个异步请求需要花费约3秒,但由于2个异步请求是并行的,总时间还是3秒左右。
package main
import (
"time"
"math/rand"
"fmt"
)
func longTimeRequest() <-chan int32 {
r := make(chan int32)
go func() {
// 模拟异步请求。
time.Sleep(time.Second * 3)
r <- rand.Int31n(100)
}()
return r
}
func sumSquares(a, b int32) int32 {
return a*a + b*b
}
func main() {
rand.Seed(time.Now().UnixNano())
a, b := longTimeRequest(), longTimeRequest()
fmt.Println(sumSquares(<-a, <-b))
}
和上面例子类似,下面的例子中,sumSquares
的2个参数都需要通过异步请求获得。不同的是,longTimeRequest
函数接受的参数是一个只能发送数据的channel
。
package main
import (
"time"
"math/rand"
"fmt"
)
func longTimeRequest(r chan<- int32) {
// 模拟异步请求。
time.Sleep(time.Second * 3)
r <- rand.Int31n(100)
}
func sumSquares(a, b int32) int32 {
return a*a + b*b
}
func main() {
rand.Seed(time.Now().UnixNano())
ra, rb := make(chan int32), make(chan int32)
go longTimeRequest(ra)
go longTimeRequest(rb)
fmt.Println(sumSquares(<-ra, <-rb))
}
事实上,以上例子可以简化为只使用一个channel
。
...
// 这里的Channel既可以是阻塞的,又可以是非阻塞的。
results := make(chan int32, 2)
go longTimeRequest(results)
go longTimeRequest(results)
fmt.Println(sumSquares(<-results, <-results))
}
这类操作很常见,下面还会看到。
这是上个例子的一个加强版。
有时候,一个数据可以从多个源获得。为了降低延迟,我们可以同时向这些源发送异步请求,选取最快的相应的数据作为结果。受到第一个数据后,其他请求就可以终止。
注意,如果有N
个源,channel
的缓冲区大小至少为N - 1
。否则,有些goroutine
会永远卡住。
package main
import (
"fmt"
"time"
"math/rand"
)
func source(c chan<- int32) {
ra, rb := rand.Int31(), rand.Intn(3) + 1
// 等待1秒、2秒或3秒。
time.Sleep(time.Duration(rb) * time.Second)
c <- ra
}
func main() {
rand.Seed(time.Now().UnixNano())
startTime := time.Now()
// c必须是一个非阻塞的Channel
c := make(chan int32, 5)
for i := 0; i < cap(c); i++ {
go source(c)
}
// 只有第一个响应的数据会被使用。
rnd := <- c
fmt.Println(time.Since(startTime))
fmt.Println(rnd)
}
各种channel
都可以变成非阻塞的,这样就不用等待请求方从channel
把结果取出。
有时候,异步请求不一定保证能返回数据。由于各种各样的原因,可能会返回一个错误。我们可以定义一个struct
(比如struct{v T; err error}
)或一个空的interface
作为channel
的元素类型。
有时候,异步请求可能需要花费更多时间,甚至永远不返回。我们可以设置一个超时时间,来处理这样的情况。
有时候,异步请求会返回一个序列的数据。这个我们之后会详细介绍如何处理这样的情况。