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



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

我对绕过这些数据的惯用方法有意见。每件事当它退出时,这个问题是可以的(所有较慢的goroutine都不是完成他们的工作,因为存在主要过程)。但是当我们取消注释时最后一行(与睡眠)我们可以看到其他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成为一个缓冲通道,每个查询有一个空间:ch := make(chan Result, len(conns))。这样,每个查询都可以运行到完成,并且不会阻塞通道写入。

CCD_ 2可以读取一次并返回第一个结果。当所有其他goroutine完成时,通道最终将被垃圾收集,一切都将消失。有了你的无缓冲通道,你就创造了很多永远无法终止的goroutine。

编辑:如果你想取消飞行中的请求,这可能会变得非常困难。一些操作和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,而是数据库调用或其他什么,那么您需要研究是否可以使用类似的取消机制。

最新更新