Я пытаюсь собрать ошибки из горутин в цикле, но не понимаю, как это должно правильно работать 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)
}
как мне правильно собрать массив ошибок и выполнить все группы ожидания?
go run -race .
: ================== Found 1 data race(s) exit status 66
в очереди go goroutine(n, errs)
Каналы в 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()
}
Кроме того, у вас есть несколько ошибок и неточностей, которые я рефакторил в вашем коде:
errs
chan содержал только ошибки, пишите туда только в том случае, если ваша функция выполнена с ненулевой ошибкой.j
, поэтому был дисбаланс между функциями Add
и функцией Done
. 3.defer fmt.Println("defer done")
после defer wg.Done()
, но конструкции defer
s выполняются в обратном порядке, чем они были указаны, поэтому было бы правильнее поставить defer fmt.Println("defer done")
перед defer wg.Done()
, чтобы «отложить выполнение» действительно сигнализировало бы, что все предыдущие defer
s были выполнены.Как я могу сохранить все ошибки в срезе? Я думаю, добавление внутри горутины вызовет состояние гонки
Условия гонки возникают, когда у вас есть несколько горутин, и одна из них записывает некоторые данные (не имеет значения, какая операция выполняет другие горулины, этого достаточно для гонки данных, когда пишет только одна горутина). Если вы будете писать свои ошибки внутри горутины вместо fmt.Println(e)
, вы можете дождаться ее окончания с помощью WaitGroup
, иначе, если вы хотите прочитать этот фрагмент в своей основной функции одновременно с горутиной, которая записывает в этот фрагмент, вам следует использовать мьютекс.
Такое решение, как это, позволит вам собирать ошибки одновременно, а также гарантирует, что они все будут собраны.