第一个完成时如何安全地绕过其他 goroutines 的结果

How to safely bypass results from other goroutines when first is completed

我想向多个服务器请求数据(例如多个只读副本)。 在这个任务中最重要的是速度,所以应该提供第一个结果 其他的都可以忽略。

我对绕过此数据的惯用方法有疑问。一切 这个问题在它退出时没问题(所有较慢的 goroutines 都不是 完成他们的工作,因为主要过程存在)。但是当我们取消注释时 最后一行(带 Sleep)我们可以看到其他 goroutines 也在做他们的工作。

现在我正在通过通道推送数据,有什么办法可以不推送它们吗?

处理此类问题的安全好方法是什么?

package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"
)

type Result int

type Conn struct {
    Id int
}

func (c *Conn) DoQuery(params string) Result {
    log.Println("Querying start", params, c.Id)
    time.Sleep(time.Duration(rand.Int31n(1000)) * time.Millisecond)
    log.Println("Querying end", params, c.Id)

    return Result(1000 + c.Id*c.Id)
}

func Query(conns []Conn, query string) Result {
    ch := make(chan Result)
    for _, conn := range conns {
        go func(c Conn) {
            ch <- c.DoQuery(query)
        }(conn)
    }

    return <-ch
}

func main() {
    conns := []Conn{Conn{1}, Conn{2}, Conn{3}, Conn{4}, Conn{5}}
    result := Query(conns, "query!")
    fmt.Println(result)
    // time.Sleep(time.Minute)
}

我的建议是让 ch 成为一个缓冲通道,每个查询有一个 space:ch := make(chan Result, len(conns))。这样每次查询都可以运行完成,并且不会阻塞在channel上写。

Query 可以读取一次,return 第一个结果。当所有其他 goroutines 完成时,通道最终将被垃圾收集,一切都会消失。使用无缓冲通道,您创建了许多永远不会终止的 goroutines。

编辑: 如果你想取消飞行中的请求,它会变得更加困难。一些操作和 api 提供取消,而另一些则不提供。对于 http 请求,您可以在请求结构中使用 Cancel 字段。只需提供一个您可以关闭以取消的频道:

func (c *Conn) DoQuery(params string, cancel chan struct{}) Result {
    //error handling omitted. It is important to handle errors properly. 
    req, _ := http.NewRequest(...)
    req.Cancel = cancel
    resp, _ := http.DefaultClient.Do(req)
    //On Cancellation, the request will return an error of some kind.
    return readData(resp)
}
func Query(conns []Conn, query string) Result {
    ch := make(chan Result)
    cancel := make(chan struct{})
    for _, conn := range conns {
        go func(c Conn) {
            ch <- c.DoQuery(query,cancel)
        }(conn)
    }

    first := <-ch
    close(cancel)
    return first
}

如果有大量您不关心的阅读请求,这可能会有所帮助,但它实际上可能会也可能不会取消远程服务器上的请求。如果您的查询不是 http,而是数据库调用或其他方式,您将需要查看是否有类似的取消机制可以使用。