Сохранение порядка входных и выходных данных при использовании горутин
Имеется функция, которая выполняет переданные ей функции параллельно, собирает результат и возвращает:
func gather(funcs []func() any) []any {
var res []any
stream := make(chan any, len(funcs))
for _, elem := range funcs {
go func(elem func() any) {
stream <- elem()
}(elem)
}
for i := 0; i < len(funcs); i++ {
res = append(res, <-stream)
}
return res
}
В эту функцию отправляются функции считающие квадрат с задержкой:
func squared(n int) func() any {
return func() any {
time.Sleep(time.Duration(n) * 100 * time.Millisecond)
return n * n
}
}
Из main() они отправляются так:
funcs := []func() any{squared(4), squared(3), squared(2)}
nums := gather(funcs)
Проблема состоит в том, что в рамках задачи необходимо вернуть результаты ровно в том же порядке, что они были отправлены. Для примера выше правильным выводом будет: [16 9 4], по факту конечно же получаем [4 9 16]. Насколько я понимаю, вызвано это тем, что функции с меньшим входным числом отрабатывают немного быстрее и результат попадает в буферизированный канал раньше. Я пробовал использовать WaitGroup, например так:
func gather(funcs []func() any) []any {
var wg sync.WaitGroup
wg.Add(len(funcs))
var res []any
stream := make(chan any, len(funcs))
for _, elem := range funcs {
go func(elem func() any) {
defer wg.Done()
stream <- elem()
}(elem)
}
wg.Wait()
for i := 0; i < len(funcs); i++ {
res = append(res, <-stream)
}
return res
}
Пробовал использовать канал завершения <-done. Все мои попытки либо не приводят к желаемому результату, либо убивают всю идею многопоточности (например, ожидать завершения горутины в каждой итерации цикла). Что я упускаю? Какой есть метод сохранения порядка вывода? Я не разбираюсь в JS, но насколько я понимаю в других языках это делается с помощью async/await и например Promise.all (в JS). Как это реализовать в моих условиях на Golang?