不在 main 中的 GoRoutines 最终陷入死锁

问题描述 投票:0回答:1

我编写 Go 一段时间了,但最近才需要实际利用 goroutine/异步操作。我很难理解事情应该如何运作。

  • 我有一个 main 函数,它只是我的 cli 工具的入口点(因此它只运行 cmd.ExecuteContext() 并检查错误)。
  • 我有一个命令,
    project doSomething
    ,需要在 for 循环中运行一些 go 例程。
  • 我不太确定问题是什么,我已经点击了:
    fatal error: all goroutines are asleep - deadlock!
    但是当我认为我已经解决了我的命令似乎永远挂起时(我认为当其中一个 go 例程遇到错误时就会发生这种情况。 ..?).

有人可以帮助我理解我应该在这里实现的模式吗?

main.go

func main() {
    ctx := context.Background()
    if err := cmd.NewRootCommand().ExecuteContext(ctx); err != nil {
        log.Err(err)
        os.Exit(1)
    }
}

另一个文件.go

func NewRootCommand() *cobra.Command {
    rootCmd.AddCommand(newProjectCmd())
    return rootCmd
}

func newProjectCmd() *cobra.Command {
    projCmd := &cobra.Command{
        Use:   "project",
        Short: "Interact with groups",
    }
    projCmd.AddCommand(newDoSomethingCmd())
    return projCmd
}

func newDoSomethingCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use: "doSomething",
        Run: func(cmd *cobra.Command, args []string) {
            
            // stuff happens here, but no go routines or anything.

            err := project.DoSomething(cmd.Context())
            if err != nil {
                os.Exit(1)
            }
        },
    }
    return cmd
}

lastfile.go(项目包)

func DoSomething(ctx context.Context) error {
        // lots of stuff happens
        err := firstFunction(ctx) 
        if err != nil {
            return err
        }
        return nil
}

func firstFunction(ctx context.Context) error {
        // stuff here no go routines
        err := functionWithGoRoutines(ctx) 
        if err != nil {
            return err
        }

        // gets called multiple times with different parameters -- at this point I want the above to FINISH though before moving on to here
        err := functionWithGoRoutines(ctx) 
        if err != nil {
            return err
        }
        return nil
}

// UPDATED
func functionWithGoRoutines(ctx context.Context, items []string) error {
    errors := make(chan error, 0)
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        i := item
        go func() {
            defer wg.Done()

            out, err := doSomethingToItemThatCanReturnError(ctx, i)
            if err != nil {
                errors <- fmt.Errorf("erro: %v", err)
                return
            }
            fmt.Println(out)
        }()
    }

    wg.Wait()
    close(errors)

    var err error
    if len(errors) > 0 {
        err = fmt.Errorf("multiple errors occurred: %v", errors)
    }
    return err
}

如果这个问题已经在其他地方得到回答而我错过了,请告诉我!据我所知,没有什么可以准确描述我的情况。如果我想做的事情有任何不清楚的地方,请告诉我!

我的最终目标是拥有一个循环遍历切片的函数,并为切片中的每个项目启动一个 go 例程。我希望每个项目的 go 例程在退出外部函数之前都有时间完成。我想收集错误(如果有)并在所有 go 例程完成后返回它们。

我还尝试在 main.go 中创建等待组,并将其一直传递到我的函数。我看到某个地方我应该在全球范围内声明它...但不确定在哪里?

go
1个回答
0
投票

如果发生错误,你的代码将会挂起,因为发生错误的 goroutine 会阻止尝试写入错误通道。没有 goroutine 从中读取数据。

你必须将等待放在 waitgroup 上或监听错误通道到它自己的 goroutine 中:

func functionWithGoRoutines(ctx context.Context, items []string) error {
    errors := make(chan error, 0)
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        i := item
        go func() {
            defer wg.Done()

            out, err := doSomethingToItemThatCanReturnError(ctx, i)
            if err != nil {
                errors <- fmt.Errorf("erro: %v", err)
                return
            }
            fmt.Println(out)
        }()
    }

    go func() {
       wg.Wait()
       close(errors)
    }()

    errs:=make([]error,0)
    for err:=range errors {
       errs=append(errs,err)
    }
    if len(errs)==0 {
       return nil
    }

    return errors.Join(errs...)
}
© www.soinside.com 2019 - 2024. All rights reserved.