如何在第一次完成时安全地绕过其他goroutine的结果

Jac*_*cki 2 channel go goroutine

我想问几个服务器的数据(例如多个只读副本).在这项任务中,最重要的是速度,因此应该提供第一个结果,并且可以忽略所有其他结果.

我有绕过这些数据的惯用方法的问题.当它退出时,所有有这个问题的东西都是可以的(所有较慢的goroutine都没有完成它们的工作,因为主进程存在).但是当我们取消注释最后一行(使用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)
}
Run Code Online (Sandbox Code Playgroud)

cap*_*aig 5

我的建议是使每个查询有一个空格的缓冲通道:ch := make(chan Result, len(conns)).这样每个查询都可以运行完成,并且不会阻塞通道写入.

Query可以读取一次并返回第一个结果.当所有其他goroutine完成后,该频道最终将被垃圾收集,一切都将消失.使用无缓冲的通道,您可以创建许多永远无法终止的goroutine.

编辑:如果你想取消飞行中的请求,它可能会变得更加困难.一些操作和apis提供取消,而其他操作和apis没有.使用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
}
Run Code Online (Sandbox Code Playgroud)

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