идти не собирать все объекты из канала

77
6

У меня есть одна go-routine чтобы добавить объекты в канал, а затем у меня есть 4 go-routines для обработки объектов канала. Обработка - это не что иное, как добавление объектов в массив. Но в несколько раз объекты отсутствуют в конечном массиве. Поэтому я предполагаю, что в какой-то момент канал перестает собирать объекты. У меня есть следующий код:

package main

import (
"log"
"sync"
)

func main() {
j := 0
for {
if j == 10 {
break
}
wg := sync.WaitGroup{}
months := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"}
hits := make(chan string)
i := 0
wg.Add(1)
go func() {
defer close(hits)
for {
if i == 25 {
wg.Done()
return
}
for _, month := range months {
hits <- month
}
i++
}
}()

temp := []string{}
for updateWorker := 1; updateWorker <= 4; updateWorker++ {
wg.Add(1)
go func() {
for hit := range hits {
temp = append(temp, hit)
}
wg.Done()
return
}()
}

wg.Wait()

log.Printf("length of temp %+v\n", len(temp))
j++
}
}

Я использую sync библиотеку для синхронизации процедур. Я повторяю один и тот же процесс 10 раз, чтобы проверить, является ли результат последовательным. Я ожидаю выхода следующим образом:

length of temp 175

Это 175, потому что я отправляю 7-месячные строки 25 раз. Но иногда выход меньше 175, я не знаю, почему. Я немного начинаю про румы. Так может кто-нибудь, пожалуйста, помогите мне найти причину? Благодарю.

спросил(а) 2021-01-25T20:39:46+03:00 4 месяца, 4 недели назад
1
Решение
100

Проблема в том, что updateWorker все собирает результаты из канала hits (пока это так хорошо), и все они сохраняют результат в локальной переменной temp несинхронизированной. Это не нормально.

Доступ ко всем переменным из нескольких goroutines (где, по крайней мере, один из них - запись) должен быть синхронизирован.

Если вы запустите его с включенным детектором гонки, он кричит о гонках данных (go run -race app.go).

Он сразу же дает достоверные результаты, если вы уменьшите число updateWorker для updateWorker до 1, потому что тогда мы исключаем исходный источник данных вашего приложения:

for updateWorker := 1; updateWorker <= 1; updateWorker++ {
// ...
}

Если вы хотите сохранить несколько updateWorker, их доступ к общей переменной temp должен быть синхронизирован.

С помощью sync.Mutex:

temp := []string{}
mux := sync.Mutex{}
for updateWorker := 1; updateWorker <= 4; updateWorker++ {
wg.Add(1)
go func() {
for hit := range hits {
mux.Lock()
temp = append(temp, hit)
mux.Unlock()
}
wg.Done()
return
}()
}

Также обратите внимание, что в этом простом примере вы ничего не получаете, используя несколько updateWorker, добавив вышеупомянутую синхронизацию (блокировку), даже делает ее менее эффективной по сравнению с наличием только одного из них.

Чтобы правильно распределить работу и собрать результаты, проверьте этот ответ: Является ли это идиоматическим пулом рабочих потоков в Go?

ответил(а) 2021-01-25T20:39:46+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема