本文节选翻译自https://go101.org/article/channel-use-cases.html

Go语言:使用Channel实现Future和Promise

futurepromise在很多语言中都很常用,它们通常被用来处理异步请求。

函数返回时,返回只能接受数据的Channel

在下面的例子里,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))
}

传参数时,传递只能发送数据的Channel

和上面例子类似,下面的例子中,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的元素类型。

有时候,异步请求可能需要花费更多时间,甚至永远不返回。我们可以设置一个超时时间,来处理这样的情况。

有时候,异步请求会返回一个序列的数据。这个我们之后会详细介绍如何处理这样的情况。