Ускорение параллельного получения данных о пользователях за фиксированный срок

Ровно 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. Повторяющиеся имена дают последнее полученное значение. Для неограниченного списка нужен пул работников; тогда время неизбежно зависит от количества задач.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее