美文网首页
go语言的errgroup源码分析和场景应用

go语言的errgroup源码分析和场景应用

作者: 鸿雁长飞光不度 | 来源:发表于2023-03-27 22:42 被阅读0次

    在Go语言中,经常需要同时执行多个任务。当这些任务之间相互独立时,可以使用并发来提高程序的性能。但是在多任务执行的过程中,我们常常会遇到一些错误,例如网络连接中断、文件读取失败等。如何处理这些错误是一个非常重要的问题。Go语言提供了一个称为errgroup的库来解决这个问题。

    1. 实现原理

    errgroup是Go语言标准库中的一个包,它提供了一种方便的方法来处理并发任务中的错误。errgroup的实现原理主要是基于两个特性:goroutine和context。

    1.1 Goroutine

    Goroutine是Go语言中的轻量级线程,它能够在并发执行的过程中节省资源和时间。通过使用Goroutine,可以实现高效的并发执行。

    1.2 Context

    Context是Go语言中的一个重要的概念,它提供了一种方式来跨Goroutine传递请求相关的值、取消信号和超时信号。Context可以帮助我们管理Goroutine的生命周期,以避免资源泄漏。

    1.3 errgroup的实现原理

    errgroup通过结合Goroutine和Context,实现了一个可以同时执行多个任务的机制。当执行任务的过程中出现错误时,errgroup会将错误信息返回给调用者。在errgroup中,每个任务都是一个Goroutine,它们共享一个Context。当Context被取消时,所有的Goroutine都会被取消。

    当一个Goroutine出现错误时,errgroup会通过Context来通知其他的Goroutine停止执行。这样可以确保程序在出现错误时能够及时停止,避免资源的浪费。

    2. 应用案例

    在需要下载多个文件时,我们可以使用 errgroup 包来并发下载这些文件,并在任意一个下载任务出错时立即取消所有下载任务的执行,从而避免浪费时间和资源。

    2.1 并发下载多个文件

    package main
    
    import (
        "context"
        "errors"
        "fmt"
        "io"
        "net/http"
        "os"
        "path/filepath"
    
        "golang.org/x/sync/errgroup"
    )
    
    func main() {
        var urls = []string{
            "https://www.example.com/image1.jpg",
            "https://www.example.com/image2.jpg",
            "https://www.example.com/image3.jpg",
        }
    
        ctx := context.Background()
        g, ctx := errgroup.WithContext(ctx)
    
        for _, url := range urls {
            url := url // 注意要重新声明一个 url,否则会出现循环变量在闭包中的错误使用
            g.Go(func() error {
                resp, err := http.Get(url)
                if err != nil {
                    return err
                }
                defer resp.Body.Close()
    
                if resp.StatusCode != http.StatusOK {
                    return fmt.Errorf("HTTP error: %d", resp.StatusCode)
                }
    
                // 获取文件名
                filename := filepath.Base(url)
    
                // 创建文件
                file, err := os.Create(filename)
                if err != nil {
                    return err
                }
                defer file.Close()
    
                // 将响应体内容写入文件
                _, err = io.Copy(file, resp.Body)
                if err != nil {
                    return err
                }
    
                return nil
            })
        }
    
        // 等待所有任务执行完成
        if err := g.Wait(); err != nil {
            fmt.Println("Error:", err)
        }
    }
    
    

    2.2 并发执行多个数据库查询

    package main
    
    import (
        "context"
        "database/sql"
        "errors"
        "fmt"
    
        _ "github.com/go-sql-driver/mysql"
        "golang.org/x/sync/errgroup"
    )
    
    func main() {
        dsn := "user:password@tcp(127.0.0.1:3306)/testdb"
    
        db, err := sql.Open("mysql", dsn)
        if err != nil {
            fmt.Println("Failed to connect to database:", err)
            return
        }
        defer db.Close()
    
        ctx := context.Background()
        g, ctx := errgroup.WithContext(ctx)
    
        // 查询语句列表
        var queries = []string{
            "SELECT COUNT(*) FROM users",
            "SELECT COUNT(*) FROM products",
            "SELECT COUNT(*) FROM orders",
        }
    
        for _, query := range queries {
            query := query // 注意要重新声明一个 query,否则会出现循环变量在闭包中的错误使用
            g.Go(func() error {
                rows, err := db.Query(query)
                if err != nil {
                    return err
                }
                defer rows.Close()
    
                var count int
                if rows.Next() {
                    if err := rows.Scan(&count); err != nil {
                        return err
                    }
                }
    
                fmt.Println("Result:", count)
    
                return nil
            })
        }
    
       // 等待所有任务执行完成
        if err := g.Wait(); err != nil {
            fmt.Println("Error:", err)
        }
    

    2.3 并发启动多个 HTTP 服务器

    package main
    
    import (
        "context"
        "errors"
        "fmt"
        "net/http"
    
        "golang.org/x/sync/errgroup"
    )
    
    func main() {
        var addrs = []string{
            ":8080",
            ":8081",
            ":8082",
        }
    
        ctx := context.Background()
        g, ctx := errgroup.WithContext(ctx)
    
        for _, addr := range addrs {
            addr := addr // 注意要重新声明一个 addr,否则会出现循环变量在闭包中的错误使用
            g.Go(func() error {
                fmt.Println("Starting server at", addr)
                err := http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                    fmt.Fprintf(w, "Hello, world!")
                }))
                if err != nil {
                    return err
                }
    
                return nil
            })
        }
    
        // 等待所有任务执行完成
        if err := g.Wait(); err != nil {
            fmt.Println("Error:", err)
        }
    }

    相关文章

      网友评论

          本文标题:go语言的errgroup源码分析和场景应用

          本文链接:https://www.haomeiwen.com/subject/hdxtddtx.html