сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · Async и tokio

Вопросы по async и tokio на собеседовании Rust

Async в Rust живёт по правилам, которых нет в других языках: future не делает ничего, пока его не опросят, отмена происходит молча в точке ожидания, а один блокирующий вызов останавливает целый воркер вместе со всеми его задачами. Спрашивают не синтаксис, а именно эти последствия.

72 вопросов в банке·6 подтем·ниже разбор 9

Что спрашивают

Из чего состоит тема

Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.

Разборы подтем

Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.

Примеры вопросов с разбором

  1. #rs_async_pitfalls1 / 9
    Почему нельзя держать std::sync::MutexGuard через точку await?
    A)Мьютекс отравится при первом же переключении задачи
    B)Guard освободится автоматически при await и запись потеряется
    C)Guard не Send: задача не соберётся или задушит остальных
    D)Компилятор запрещает блокировки внутри async-функций
    показать ответ и разбор
    +C)Guard не Send: задача не соберётся или задушит остальных

    // разбор: Обычный guard привязан к потоку, который взял блокировку, и не Send — задача с ним не собирается для многопоточного рантайма. Но даже там, где соберётся, держать блокировку через ожидание опасно: пока задача ждёт ответа сети, другие стоят в очереди. Правильный путь — сузить область жизни guard или взять асинхронный мьютекс.

  2. #rs_async_traits2 / 9
    Что изменилось с async fn в трейтах, начиная с версии 1.75?
    A)Они стали работать в трейт-объектах наравне с обычными методами
    B)Они получили автоматическое ограничение Send для возвращаемого future
    C)Их можно объявлять прямо в трейте, без внешнего макроса
    D)Они перестали требовать рантайма для выполнения
    показать ответ и разбор
    +C)Их можно объявлять прямо в трейте, без внешнего макроса

    // разбор: Раньше async в трейте разворачивали через async_trait, и каждый вызов стоил Box и аллокации. Теперь компилятор поддерживает такие методы напрямую, возвращая непоименованный тип future. Ограничения остались: dyn-совместимости у таких трейтов нет, а Send для результата приходится требовать отдельно.

  3. #rs_future3 / 9
    Что произойдёт, если вызвать async-функцию и не написать .await?
    A)Тело выполнится в фоне, результат будет отброшен
    B)Функция выполнится синхронно, как обычная
    C)Ничего не выполнится: future никто не опрашивает
    D)Рантайм запустит её при первой свободной возможности
    показать ответ и разбор
    +C)Ничего не выполнится: future никто не опрашивает

    // разбор: Вызов async fn только строит объект-future и стоит десятки наносекунд: тело не начинает работать, пока его кто-то не опросит. Компилятор напоминает предупреждением must_use про неиспользованный future. Чтобы задача пошла в фон именно как фоновая, её отдают рантайму через spawn — тогда await не нужен сразу.

  4. #rs_runtime4 / 9
    Почему для async-кода нужен внешний рантайм вроде tokio?
    A)Язык даёт синтаксис и трейт Future, но исполнителя в std нет
    B)tokio нужен только для сети: без ввода-вывода хватает std
    C)Стандартный рантайм есть, но он однопоточный и годится лишь для тестов
    D)Рантайм требуется, чтобы компилятор мог разрезать async fn на состояния
    показать ответ и разбор
    +A)Язык даёт синтаксис и трейт Future, но исполнителя в std нет

    // разбор: В стандартной библиотеке есть Future, Poll и Waker — контракт. Кто и как опрашивает задачи, следит за таймерами и сокетами, решает рантайм: tokio, smol, async-std. Это осознанное решение: серверу нужен многопоточный планировщик, встраиваемой системе — крошечный исполнитель без ОС.

  5. #rs_select_cancel5 / 9
    Что делает макрос select!?
    A)Ждёт первую готовую ветку, остальные бросает незавершёнными
    B)Выбирает ветку по приоритету, заданному порядком объявления
    C)Дожидается всех веток и возвращает их результаты кортежем
    D)Запускает ветки в разных задачах и собирает результаты
    показать ответ и разбор
    +A)Ждёт первую готовую ветку, остальные бросает незавершёнными

    // разбор: Классический приём — гонка операции с таймером: кто первым готов, тот и выиграл. Проигравшие future уничтожаются прямо в точке ожидания, то есть отменяются. Отсюда главный вопрос при использовании: безопасно ли прерывать проигравшую ветку в произвольной точке.

  6. #rs_tasks6 / 9
    Чем tokio::spawn отличается от простого .await на том же future?
    A)spawn выполняет future синхронно и сразу возвращает результат
    B)spawn отдаёт задачу рантайму, и она идёт конкурентно с текущей
    C)spawn ставит задачу в очередь и запускает после завершения текущей
    D)spawn создаёт системный поток под каждую задачу
    показать ответ и разбор
    +B)spawn отдаёт задачу рантайму, и она идёт конкурентно с текущей

    // разбор: await исполняет future прямо в текущей задаче: две операции по 50 мс подряд дадут 100 мс. spawn регистрирует задачу в рантайме, и она крутится параллельно, а JoinHandle позволяет забрать результат позже — те же две операции займут 50 мс. Плата — требование 'static и Send для передаваемого future.

  7. #rs_async_pitfalls7 / 9
    Когда tokio::sync::Mutex выгоднее std::sync::Mutex?
    A)Когда конкуренция высокая: асинхронный мьютекс быстрее
    B)В асинхронном коде обычный мьютекс не работает
    C)Когда защищаемые данные не реализуют Send
    D)Когда блокировку нужно удерживать через await
    показать ответ и разбор
    +D)Когда блокировку нужно удерживать через await

    // разбор: Асинхронный мьютекс дороже: ожидание идёт через планировщик, а не через примитив ОС. Его берут ровно тогда, когда блокировку приходится держать через точку ожидания — например, вокруг соединения с базой. Для короткой правки структуры в памяти обычный std-мьютекс проще и быстрее, и это рекомендация самой документации tokio.

  8. #rs_async_traits8 / 9
    Зачем нужен async_trait, если язык уже умеет async fn в трейтах?
    A)Он нужен для совместимости со старыми версиями компилятора
    B)Он боксирует future и делает трейт dyn-совместимым
    C)Он добавляет ограничение Send автоматически ко всем методам
    D)Он ускоряет вызовы за счёт статической диспетчеризации
    показать ответ и разбор
    +B)Он боксирует future и делает трейт dyn-совместимым

    // разбор: Встроенный async fn возвращает анонимный тип, у которого нет фиксированного размера, — в vtable такой метод не положить. Макрос переписывает сигнатуру в Pin<Box<dyn Future>>, и трейт снова годится для Box<dyn Trait>: платим аллокацией на вызов, получаем возможность держать разные реализации в одном списке.

  9. #rs_future9 / 9
    Во что компилятор превращает async fn?
    A)В конечный автомат с состояниями на каждой точке await
    B)В замыкание, вызываемое планировщиком по таймеру
    C)В генератор, который рантайм исполняет построчно через интерпретатор
    D)В обычную функцию, которая запускается в отдельном потоке
    показать ответ и разбор
    +A)В конечный автомат с состояниями на каждой точке await

    // разбор: Тело разрезается по точкам await: каждая становится состоянием автомата, а локальные переменные, живущие через await, переезжают в структуру состояния. Отсюда и размер future — сотня-другая байт для простой функции, — и требование Pin: у автомата бывают ссылки на собственные поля.

это 9 из 72

Ещё 63 вопросов по теме — в тренажёре, с движком повторения

Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.

Частые вопросы