Собирать ошибки из горутин, вложенных в циклы

Я пытаюсь собрать ошибки из горутин в цикле, но не понимаю, как это должно правильно работать https://go.dev/play/p/WrxE0vH6JSG

func init() {
    rand.Seed(1500929006430687579)
}

func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
    defer wg.Done()
    defer fmt.Println("defer done")

    fmt.Println("num ", n)
    if n == 1 {
        ch <- fmt.Errorf("error")
    } else {
        ch <- nil
    }
}

func main() {
    var wg sync.WaitGroup
    var err error
    errs := make(chan error)
    platforms := 2
    types := 3
    for j := 0; j < platforms; j++ {
        wg.Add(1)
        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }

        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }
    }
    wg.Wait()
    err = <-errs
    fmt.Println(err)
}

как мне правильно собрать массив ошибок и выполнить все группы ожидания?

Такое решение, как это, позволит вам собирать ошибки одновременно, а также гарантирует, что они все будут собраны.

Gavin 11.11.2022 21:51
go run -race .: ================== Found 1 data race(s) exit status 66 в очереди go goroutine(n, errs)
SaneQ 14.11.2022 14:21
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
2
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Каналы в Golang аналогичны пайпам в bash (|). Но в отличие от каналов bash, которые используются для передачи вывода одной команды на ввод другой команды, каналы Go используются для передачи некоторых данных между горутинами. Подробнее о каналах можно прочитать здесь. Каналы имеют пропускную способность. Когда вы не указываете пропускную способность для канала, go предполагает, что он имеет нулевую пропускную способность. Каналы с нулевой пропускной способностью часто называются unbuffered каналами, а каналы с ненулевой пропускной способностью называются buffered. Когда канал заполнен (количество элементов в канале равно пропускной способности канала), все операции записи в канале (->errs) блокируют выполнение до тех пор, пока не будет представлена ​​операция чтения (<-errs).

В вашем конкретном примере у вас есть небуферизованный канал (канал с нулевой пропускной способностью). Таким образом, любая операция записи (->errs) на вашем канале будет блокировать выполнение до тех пор, пока не будет предоставлена ​​какая-либо операция чтения, поэтому все запущенные вами горутины будут заблокированы, несмотря на то, что только одна горутина сможет продолжить операцию записи, когда поток main функция перешла к операции чтения (err = <-errs).

Чтобы решить вашу проблему, вы можете создать одну дополнительную горутину, которая будет читать из канала одновременно с горутинами, которые будут писать в канал. Это будет выглядеть так:

func init() {
    rand.Seed(1500929006430687579)
}

func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
    defer fmt.Println("defer done")
    defer wg.Done()

    fmt.Println("num ", n)
    if n == 1 {
        ch <- fmt.Errorf("error")
    }
}

func main() {
    var wg sync.WaitGroup
    errs := make(chan error)
    platforms := 2
    types := 3

    go func() {
        for e := range errs {
            fmt.Println(e)
        }
    }()

    for j := 0; j < platforms; j++ {
        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }

        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }
    }
    wg.Wait()
}

Кроме того, у вас есть несколько ошибок и неточностей, которые я рефакторил в вашем коде:

  1. Вы не должны писать nil в канале с ошибками. Если вы хотите, чтобы errs chan содержал только ошибки, пишите туда только в том случае, если ваша функция выполнена с ненулевой ошибкой.
  2. У вас был один лишний wd.Add(1) в начале цикла j, поэтому был дисбаланс между функциями Add и функцией Done. 3.
  3. Кроме того, вы добавляете defer fmt.Println("defer done") после defer wg.Done(), но конструкции defers выполняются в обратном порядке, чем они были указаны, поэтому было бы правильнее поставить defer fmt.Println("defer done") перед defer wg.Done(), чтобы «отложить выполнение» действительно сигнализировало бы, что все предыдущие defers были выполнены.

Как я могу сохранить все ошибки в срезе? Я думаю, добавление внутри горутины вызовет состояние гонки

SaneQ 14.11.2022 14:15

Условия гонки возникают, когда у вас есть несколько горутин, и одна из них записывает некоторые данные (не имеет значения, какая операция выполняет другие горулины, этого достаточно для гонки данных, когда пишет только одна горутина). Если вы будете писать свои ошибки внутри горутины вместо fmt.Println(e), вы можете дождаться ее окончания с помощью WaitGroup, иначе, если вы хотите прочитать этот фрагмент в своей основной функции одновременно с горутиной, которая записывает в этот фрагмент, вам следует использовать мьютекс.

cooleck 14.11.2022 15:43

Другие вопросы по теме