web-dev-qa-db-ja.com

複数のゴルーチンの結果を待つ

異なる結果とエラーを返す2つの関数をgoで非同期に実行し、それらが終了するのを待って両方の結果を出力する方法を探しています。また、関数の1つがエラーを返した場合、別の関数を待ちたくなく、エラーを出力するだけです。たとえば、私はこの関数を持っています:

func methodInt(error bool) (int, error) {
    <-time.NewTimer(time.Millisecond * 100).C
    if error {
        return 0, errors.New("Some error")
    } else {
        return 1, nil
    }
}

func methodString(error bool) (string, error) {
    <-time.NewTimer(time.Millisecond * 120).C
    if error {
        return "", errors.New("Some error")
    } else {
        return "Some result", nil
    }
}

ここ https://play.golang.org/p/-8StYapmlg は私がそれを実装した方法ですが、コードが多すぎると思います。 interface {}を使用すると簡略化できますが、この方法は使いたくありません。たとえば、async/awaitを使用してC#で実装できるように、もっと単純なものが必要です。おそらく、そのような操作を単純化するいくつかのライブラリがあります。

更新:ご回答ありがとうございます!私が助けを得たのは素晴らしいです!私はWaitGroupの使い方が好きです。それは明らかにコードを変更に対してよりロバストにするので、最終的にメソッドの正確な数を変更することなく、別の非同期メソッドを簡単に追加できます。ただし、C#のコードと比較すると、まだ多くのコードがあります。 goでは、メソッドを非同期として明示的にマークする必要がないことを知っています。実際にタスクを返すようにしていますが、メソッドの呼び出しははるかに単純に見えます。たとえば、このリンクを検討してください 実際には例外をキャッチすることも必要です =ちなみに、私のタスクでは、非同期で実行したい関数の戻りタイプを知る必要がないことがわかりました。とにかくjsonにマーシャリングされるため、のエンドポイントレイヤーで複数のサービスを呼び出すだけです。ゴーキット。

8
user1593294

エラーと結果用に2つのチャネルを作成し、エラーがない場合は最初にエラーを読み取り、次に結果を読み取る必要があります。このサンプルは、ユースケースで機能するはずです。

package main

import (
    "errors"
    "sync"
)

func test(i int) (int, error) {
    if i > 2 {
        return 0, errors.New("test error")
    }
    return i + 5, nil
}

func test2(i int) (int, error) {
    if i > 3 {
        return 0, errors.New("test2 error")
    }
    return i + 7, nil
}

func main() {
    results := make(chan int, 2)
    errors := make(chan error, 2)
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        result, err := test(3)
        if err != nil {
            errors <- err
            return
        }
        results <- result
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        result, err := test2(3)
        if err != nil {
            errors <- err
            return
        }
        results <- result
    }()

    // here we wait in other goroutine to all jobs done and close the channels
    go func() {
        wg.Wait()
        close(results)
        close(errors)
    }()
    for err := range errors {
        // here error happend u could exit your caller function
        println(err.Error())
        return

    }
    for res := range results {
        println("--------- ", res, " ------------")
    }
}
7
Jeyem

ここではsync.WaitGroupを使用できると思います。異なる動的な数のゴルーチンを待つことができます。

5
Eugene Lisitsky

2つのgoルーチンを非同期で実行し、エラーが発生した場合に両方が終了するかプログラムを終了するのを待つ方法の、より小さな自己完結型の例を作成しました(説明については以下を参照)。

package main

import (
    "errors"
    "fmt"
    "math/Rand"
    "time"
)

func main() {
    Rand.Seed(time.Now().UnixNano())

    // buffer the channel so the async go routines can exit right after sending
    // their error
    status := make(chan error, 2)

    go func(c chan<- error) {
        if Rand.Intn(2) == 0 {
            c <- errors.New("func 1 error")
        } else {
            fmt.Println("func 1 done")
            c <- nil
        }
    }(status)

    go func(c chan<- error) {
        if Rand.Intn(2) == 0 {
            c <- errors.New("func 2 error")
        } else {
            fmt.Println("func 2 done")
            c <- nil
        }
    }(status)

    for i := 0; i < 2; i++ {
        if err := <-status; err != nil {
            fmt.Println("error encountered:", err)
            break
        }
    }
}

私がしているのは、2つのgoルーチンの同期に使用されるチャネルを作成することです。書き込みと読み取りはブロックされます。チャネルはエラー値を渡すために使用され、関数が成功した場合はnilになります。

最後に、チャネルからasyncgoルーチンごとに1つの値を読み取ります。これは、値が受信されるまでブロックされます。エラーが発生した場合は、ループを終了してプログラムを終了します。

関数はランダムに成功または失敗します。

これにより、goルーチンを調整する方法が理解できることを願っています。そうでない場合は、コメントでお知らせください。

これをGo Playgroundで実行すると、Rand.Seedは何も実行せず、プレイグラウンドは常に同じ「乱数」を持つため、動作は変わりません。

0
gonutz