我编写 Go 一段时间了,但最近才需要实际利用 goroutine/异步操作。我很难理解事情应该如何运作。
- 我有一个 main 函数,它只是我的 cli 工具的入口点(因此它只运行 cmd.ExecuteContext() 并检查错误)。
- 我有一个命令
project doSomething
,需要在 for 循环中运行一些 go 例程。 - 我不太确定问题是什么,我已经遇到了:
fatal error: all goroutines are asleep - deadlock!
但是当我认为我已经解决了我的命令似乎永远挂起时(我认为当其中一个 go 例程遇到错误时会发生这种情况......?) 。
有人可以帮助我理解我应该在这里实现的模式吗?
主程序
func main() {
ctx := context.Background()
if err := cmd.NewRootCommand().ExecuteContext(ctx); err != nil {
log.Err(err)
os.Exit(1)
}
}
anotherfile.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 中创建等待组,并将其一直传递到我的函数。我看到某个地方我应该在全球范围内声明它......但不确定在哪里?
更新
@Burak Sedar 解决了我最初的问题,但我意识到在我的代码中我还有一个问题/考虑因素。
上述每个 go 例程中都有另一个需要运行的 go 例程,但是这个例程不会返回错误,并且应该在后台运行,直到“外部”go 例程完成。然后就可以取消了。
我正在这样做,但它似乎接管了外部 go 例程的等待,并导致整个命令永远不会结束:
go func() {
select {
case <-ctx.Done():
return
default:
time.Sleep(30 * time.Second)
doSomethingNewThatShouldEndOnceOuterRoutineEnds(ctx)
}
return
}()