Ровно 10 мс для любого числа пользователей гарантировать нельзя: запуск, сбор результатов и память тоже имеют цену. Контекст требует поддержки внутри fetchByName.
Ускорение параллельного получения данных о пользователях за фиксированный срок
Ровно 10 мс для любого числа пользователей гарантировать нельзя: запуск, сбор результатов и память тоже имеют цену. Контекст требует поддержки внутри fetchByName.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо доработать функцию Do, чтобы она:
- завершала обработку любого количества пользователей ровно за 10 мс;
- запускала запросы к
fetchByNameпараллельно; - корректно учитывала контекст
ctxи передавала возникающие ошибки наружу.
import (
"context"
"fmt"
"math/rand"
"time"
)
func main() {
fmt.Println(Do(context.Background(), []User{{"aaa"}, {"bbb"}, {"ccc"}, {"ddd"}, {"eee"}}))
}
type User struct {
Name string
}
func fetchByName(ctx context.Context, userName string) (int, error) {
// имитируем сетевой запрос, который занимает 10 мс
time.Sleep(10 * time.Millisecond)
return rand.Int() % 100000, nil
}
func Do(ctx context.Context, users []User) (map[string]int, error) {
resultMap := make(map[string]int)
// TODO: реализовать параллельный вызов fetchByName и собрать результаты в resultMap
// при возникновении ошибки сразу вернуть текущие данные и ошибку
// обеспечить, что общее время работы функции не превышает 10 мс независимо от количества пользователей
return resultMap, nil
}
Ответ
Ровно 10 мс для любого числа пользователей гарантировать нельзя: запуск, сбор результатов и память тоже имеют цену. Контекст требует поддержки внутри fetchByName.
Для небольшого ограниченного списка можно запускать все запросы и собирать ответы в одной горутине, без конкурентной записи в map:
func Do(ctx context.Context, users []User) (map[string]int, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
type reply struct {
name string
value int
err error
}
out := make(chan reply, len(users))
for _, user := range users {
if err := ctx.Err(); err != nil { return nil, err }
go func(user User) {
value, err := fetchByName(ctx, user.Name)
out <- reply{user.Name, value, err}
}(user)
}
result := make(map[string]int)
for range users {
select {
case <-ctx.Done():
return result, ctx.Err()
case r := <-out:
if r.err != nil { return result, r.err }
result[r.name] = r.value
}
}
return result, ctx.Err()
}
Буфер не позволяет оставшимся отправителям зависнуть после раннего возврата. В исходной fetchByName Sleep игнорирует ctx: замените ожидание выбором между таймером и ctx.Done, а реальный запрос создавайте с context. Повторяющиеся имена дают последнее полученное значение. Для неограниченного списка нужен пул работников; тогда время неизбежно зависит от количества задач.