go - 检查所有 goroutine 是否已完成而不使用 wg.Wait()

标签 go concurrency waitgroup

假设我有一个函数 IsAPrimaryColour(),它通过调用其他三个函数 IsRed()IsGreen()来工作>IsBlue()。由于这三个功能彼此相当独立,因此它们可以同时运行。返回条件为:

  1. 如果三个函数中的任何一个返回 true,则 IsAPrimaryColour() 也应该返回 true。无需等待其他 函数来完成。也就是说:如果 IsRed()trueIsGreen()<,则 IsPrimaryColour()true/em> 为 trueIsBlue()true
  2. 如果所有函数都返回 false,IsAPrimaryColour() 也应该返回 错误的。也就是说:如果 IsRed()false 并且 IsGreen()<,则 IsPrimaryColour()false/em> 为 false 并且 IsBlue()false
  3. 如果三个函数中的任何一个返回错误,IsAPrimaryColour() 还应该返回错误。无需等待其他 函数来完成,或收集任何其他错误。

我正在努力解决的问题是,如果其他三个函数返回 true,如何退出该函数,而且如果它们都返回 false,则如何等待所有三个函数完成。如果我使用sync.WaitGroup对象,我需要等待所有3个go例程完成才能从调用函数返回。

因此,我使用循环计数器来跟踪我在 channel 上收到消息的次数,并在收到所有 3 条消息后启动程序。

https://play.golang.org/p/kNfqWVq4Wix

package main

import (
    "errors"
    "fmt"
    "time"
)

func main() {
    x := "something"
    result, err := IsAPrimaryColour(x)

    if err != nil {
        fmt.Printf("Error: %v\n", err)
    } else {
        fmt.Printf("Result: %v\n", result)
    }
}

func IsAPrimaryColour(value interface{}) (bool, error) {
    found := make(chan bool, 3)
    errors := make(chan error, 3)
    defer close(found)
    defer close(errors)
    var nsec int64 = time.Now().UnixNano()

    //call the first function, return the result on the 'found' channel and any errors on the 'errors' channel
    go func() {
        result, err := IsRed(value)
        if err != nil {
            errors <- err
        } else {
            found <- result
        }
        fmt.Printf("IsRed done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
    }()

    //call the second function, return the result on the 'found' channel and any errors on the 'errors' channel
    go func() {
        result, err := IsGreen(value)
        if err != nil {
            errors <- err
        } else {
            found <- result
        }
        fmt.Printf("IsGreen done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
    }()

    //call the third function, return the result on the 'found' channel and any errors on the 'errors' channel
    go func() {
        result, err := IsBlue(value)
        if err != nil {
            errors <- err
        } else {
            found <- result
        }
        fmt.Printf("IsBlue done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
    }()

    //loop counter which will be incremented every time we read a value from the 'found' channel
    var counter int

    for {
        select {
        case result := <-found:
            counter++
            fmt.Printf("received a value on the results channel after %f nanoseconds. Value of counter is %d\n", float64(time.Now().UnixNano()-nsec), counter)
            if result {
                fmt.Printf("some goroutine returned true\n")
                return true, nil
            }
        case err := <-errors:
            if err != nil {
                fmt.Printf("some goroutine returned an error\n")
                return false, err
            }
        default:
        }

        //check if we have received all 3 messages on the 'found' channel. If so, all 3 functions must have returned false and we can thus return false also
        if counter == 3 {
            fmt.Printf("all goroutines have finished and none of them returned true\n")
            return false, nil
        }
    }
}

func IsRed(value interface{}) (bool, error) {
    return false, nil
}

func IsGreen(value interface{}) (bool, error) {
    time.Sleep(time.Millisecond * 100) //change this to a value greater than 200 to make this function take longer than IsBlue()
    return true, nil
}

func IsBlue(value interface{}) (bool, error) {
    time.Sleep(time.Millisecond * 200)
    return false, errors.New("something went wrong")
}

虽然这工作得很好,但我想知道我是否没有忽略一些语言功能以更好的方式做到这一点?

最佳答案

errgroup.WithContext可以帮助简化这里的并发性。

如果发生错误或找到结果,您希望停止所有 goroutine。如果您可以将“找到结果”表示为可区分的错误(类似于 io.EOF 的行),那么您可以使用 errgroup 的内置“取消”在第一个错误时”行为关闭整个组:

func IsAPrimaryColour(ctx context.Context, value interface{}) (bool, error) {
    var nsec int64 = time.Now().UnixNano()

    errFound := errors.New("result found")
    g, ctx := errgroup.WithContext(ctx)

    g.Go(func() error {
        result, err := IsRed(ctx, value)
        if result {
            err = errFound
        }
        fmt.Printf("IsRed done in %f nanoseconds \n", float64(time.Now().UnixNano()-nsec))
        return err
    })

    …

    err := g.Wait()
    if err == errFound {
        fmt.Printf("some goroutine returned errFound\n")
        return true, nil
    }
    if err != nil {
        fmt.Printf("some goroutine returned an error\n")
        return false, err
    }
    fmt.Printf("all goroutines have finished and none of them returned true\n")
    return false, nil
}

( https://play.golang.org/p/MVeeBpDv4Mn )

关于go - 检查所有 goroutine 是否已完成而不使用 wg.Wait(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68939352/

相关文章:

go - 如何将字符串 slice 转换为 rune slice

json - JSON嵌套结构golang

go - 无法将变量分配给 for 循环中的匿名函数

go - Go 中的接收器(方法)放在哪里?

multithreading - 限制多线程上的对象分配

java - 为什么不同步会使 ArrayList 更快但更不安全?

oracle - 给定 JDBC 连接上的并发查询?

go - 如何获取与 WaitGroup 关联的 goroutines 的数量?