惯用goroutine终止和错误处理

惯用goroutine终止和错误处理,go,channel,goroutine,Go,Channel,Goroutine,我在go中有一个简单的并发用例,我无法找到一个优雅的解决方案来解决我的问题 我想编写一个方法fetchAll,并行地从远程服务器查询未指定数量的资源。如果任何回迁失败,我想立即返回第一个错误 我的初始实现泄漏goroutines: 主程序包 进口( “fmt” “数学/兰德” “同步” “时间” ) func fetchAll()错误{ wg:=sync.WaitGroup{} 错误:=make(chan错误) 泄漏:=make(映射[int]结构{}) 延迟fmt.Println(“这些gor

我在go中有一个简单的并发用例,我无法找到一个优雅的解决方案来解决我的问题

我想编写一个方法
fetchAll
,并行地从远程服务器查询未指定数量的资源。如果任何回迁失败,我想立即返回第一个错误

我的初始实现泄漏goroutines:

主程序包
进口(
“fmt”
“数学/兰德”
“同步”
“时间”
)
func fetchAll()错误{
wg:=sync.WaitGroup{}
错误:=make(chan错误)
泄漏:=make(映射[int]结构{})
延迟fmt.Println(“这些goroutine泄漏:”,泄漏)
//并行运行所有http请求
对于i:=0;i<4;i++{
泄漏[i]=struct{}{}
工作组.添加(1)
go func(i int){
推迟工作组完成()
延迟删除(泄漏,i)
//假设它执行http请求并返回错误
time.Sleep(time.Duration(rand.Intn(100))*time.毫秒)

errs只要每个goroutine完成,您就不会泄漏任何信息。您应该创建缓冲区大小等于goroutine数量的错误通道,以便通道上的发送操作不会阻塞。每个goroutine在完成时都应该在通道上发送内容,无论成功与否。t处的循环然后,他可以迭代goroutine的数量,如果得到非nil错误则返回。您不需要WaitGroup或关闭通道的其他goroutine

我认为goroutines正在泄漏的原因是当您收到第一个错误时返回,所以其中一些仍然在运行


顺便说一句,地图不是goroutine安全的。如果您在goroutine之间共享地图,并且其中一些正在对地图进行更改,则需要使用互斥锁对其进行保护。

除了一个goroutine之外,您的所有goroutine都被泄漏,因为它们仍在等待发送到errs通道-您永远无法完成清空它的for范围。您还泄漏了goroutine谁的工作是关闭errs通道,因为waitgroup永远不会结束

(另外,正如Andy指出的,从映射中删除不是线程安全的,因此需要防止互斥)

但是,我认为这里甚至不需要映射、互斥、等待组、上下文等。我将重写整个过程,只使用基本的通道操作,如下所示:

package main

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

func fetchAll() error {
    var N = 4
    quit := make(chan bool)
    errc := make(chan error)
    done := make(chan error)
    for i := 0; i < N; i++ {
        go func(i int) {
            // dummy fetch
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            err := error(nil)
            if rand.Intn(2) == 0 {
                err = fmt.Errorf("goroutine %d's error returned", i)
            }
            ch := done // we'll send to done if nil error and to errc otherwise
            if err != nil {
                ch = errc
            }
            select {
            case ch <- err:
                return
            case <-quit:
                return
            }
        }(i)
    }
    count := 0
    for {
        select {
        case err := <-errc:
            close(quit)
            return err
        case <-done:
            count++
            if count == N {
                return nil // got all N signals, so there was no error
            }
        }
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(fetchAll())
}
主程序包
进口(
“fmt”
“数学/兰德”
“时间”
)
func fetchAll()错误{
变量N=4
退出:=make(chan bool)
errc:=制造(chan错误)
完成:=生成(更改错误)
对于i:=0;i
主程序包
进口(
“上下文”
“fmt”
“数学/兰德”
“时间”
“golang.org/x/sync/errgroup”
)
func fetchAll(ctx context.context)错误{
errs,ctx:=errgroup.WithContext(ctx)
//并行运行所有http请求
对于i:=0;i<4;i++{
errs.Go(func()错误{
//假设它执行http请求并返回错误
time.Sleep(time.Duration(rand.Intn(100))*time.毫秒)
返回fmt.Errorf(“go例行程序中的错误,bailing”)
})
}
//等待完成并返回第一个错误(如果有)
返回errs.Wait()
}
func main(){
fmt.Println(fetchAll(context.Background()))
}

下面是一个使用建议者的更完整示例。它显示处理成功的数据,并将在第一个错误时退出

主程序包
进口(
“上下文”
“fmt”
“golang.org/x/sync/errgroup”
“数学/兰德”
“时间”
)
func fetchAll()错误{
g、 ctx:=errgroup.WithContext(context.Background())
结果:=make(chan int)
对于i:=0;i<4;i++{
电流:=i
g、 Go(func()错误{
//用随机误差模拟延迟。
time.Sleep(time.Duration(rand.Intn(100))*time.毫秒)
如果rand.Intn(2)=0{
返回fmt.Errorf(“返回了goroutine%d的错误”,当前)
}
//将处理后的数据传递到通道,或接收上下文完成。
挑选{

案例结果此答案包括将回答返回到
doneData
-

主程序包
进口(
“fmt”
“数学/兰德”
“操作系统”
“strconv”
)
var doneData[]字符串//响应
func fetchAll(n int,doneCh chan bool,errCh chan error){
partialDoneCh:=制造(成串)
对于i:=0;ierrCh我同意使用缓冲通道是可行的,我试图避免这种解决方案,尽管获取的数量事先不容易知道(实际代码比示例更复杂)。ec:=chan error(nil)
很有趣,我没有
package main

import (
        "context"
        "fmt"
        "math/rand"
        "time"

        "golang.org/x/sync/errgroup"
)

func fetchAll(ctx context.Context) error {
        errs, ctx := errgroup.WithContext(ctx)

        // run all the http requests in parallel
        for i := 0; i < 4; i++ {
                errs.Go(func() error {
                        // pretend this does an http request and returns an error                                                  
                        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)                                               
                        return fmt.Errorf("error in go routine, bailing")                                                      
                })
        }

        // Wait for completion and return the first error (if any)                                                                 
        return errs.Wait()
}

func main() {
        fmt.Println(fetchAll(context.Background()))
}
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "math/rand"
    "time"
)

func fetchAll() error {
    g, ctx := errgroup.WithContext(context.Background())
    results := make(chan int)
    for i := 0; i < 4; i++ {
        current := i
        g.Go(func() error {
            // Simulate delay with random errors.
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            if rand.Intn(2) == 0 {
                return fmt.Errorf("goroutine %d's error returned", current)
            }
            // Pass processed data to channel, or receive a context completion.
            select {
            case results <- current:
                return nil
            // Close out if another error occurs.
            case <-ctx.Done():
                return ctx.Err()
            }
        })
    }

    // Elegant way to close out the channel when the first error occurs or
    // when processing is successful.
    go func() {
        g.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println("processed", result)
    }

    // Wait for all fetches to complete.
    return g.Wait()
}

func main() {
    fmt.Println(fetchAll())
}