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

Вопросы по многопоточности Rust на собеседовании

Отличие от Java и C++ в том, что гонку данных здесь ловит компилятор: Rc не уедет в другой поток, а RefCell не попадёт внутрь Arc. Поэтому вопросы быстро смещаются к тому, что компилятор не ловит: к порядку захвата замков, ложному разделению и цене синхронизации.

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

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

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

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

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

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

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

  1. #rs_atomics1 / 9
    Что делает fetch_add у AtomicUsize?
    A)Прибавляет значение и возвращает новое, блокируя другие потоки
    B)Ставит операцию в очередь, применяя её при следующем чтении
    C)Атомарно прибавляет значение и возвращает предыдущее
    D)Прибавляет значение только если оно не менялось с прошлого чтения
    показать ответ и разбор
    +C)Атомарно прибавляет значение и возвращает предыдущее

    // разбор: Это операция read-modify-write на уровне процессора: инкремент не может «потеряться» между чтением и записью, как у обычного i32. Возвращается старое значение — удобно для выдачи уникальных id. Блокировок нет, но и группу операций атомарной это не делает: два fetch_add подряд — уже не одна транзакция.

  2. #rs_channels2 / 9
    Что означает mpsc в названии std::sync::mpsc?
    A)Много отправителей, один получатель
    B)Многопоточная очередь с приоритетами
    C)Много производителей, много потребителей
    D)Один отправитель, много получателей
    показать ответ и разбор
    +A)Много отправителей, один получатель

    // разбор: Sender клонируется и раздаётся потокам, Receiver существует в одном экземпляре — отсюда multi-producer, single-consumer. Такая форма покрывает типовой сбор результатов от воркеров. Когда получателей нужно несколько, берут внешние крейты вроде crossbeam или flume.

  3. #rs_mutex3 / 9
    Почему общий счётчик оборачивают именно в Arc<Mutex<i32>>?
    A)Arc копирует счётчик в каждый поток, Mutex сводит копии в конце
    B)Arc даёт общее владение, Mutex — эксклюзивный доступ
    C)Так требует thread::spawn: он принимает только такие типы
    D)Arc защищает от гонок, Mutex продлевает жизнь значения
    показать ответ и разбор
    +B)Arc даёт общее владение, Mutex — эксклюзивный доступ

    // разбор: Два разных вопроса решаются двумя обёртками. Кому принадлежит значение, если потоков несколько — Arc с атомарным счётчиком ссылок. Кто именно сейчас его меняет — Mutex, выдающий эксклюзивный guard. По отдельности не работает: Arc<i32> не даст записи, а Mutex без Arc не переживёт передачу в потоки.

  4. #rs_par_patterns4 / 9
    Что делает замена iter() на par_iter() из rayon?
    A)Запускает по потоку на каждый элемент коллекции
    B)Переносит вычисление в асинхронный рантайм
    C)Раскладывает обход по пулу потоков, сохраняя семантику итератора
    D)Ускоряет обход за счёт векторизации на одном ядре
    показать ответ и разбор
    +C)Раскладывает обход по пулу потоков, сохраняя семантику итератора

    // разбор: rayon делит работу на куски и раздаёт их пулу воркеров с воровством задач, а API остаётся привычным — map, filter, sum. Требование одно: замыкания должны быть Send, а разделяемые данные — Sync, и это проверяет компилятор. Выигрыш появляется на счётной работе; на коротких коллекциях накладные расходы съедают пользу.

  5. #rs_send_sync5 / 9
    Что означают трейты Send и Sync?
    A)Send — передать значение в другой поток, Sync — ссылку из нескольких
    B)Send — для каналов, Sync — для мьютексов и блокировок
    C)Send — тип потокобезопасен, Sync — тип синхронизирует доступ внутри
    D)Send — значение копируется при передаче, Sync — разделяется по ссылке
    показать ответ и разбор
    +A)Send — передать значение в другой поток, Sync — ссылку из нескольких

    // разбор: Send отвечает за передачу владения через границу потока, Sync — за разделяемый доступ: T: Sync ровно тогда, когда &T: Send. Оба трейта пустые и выводятся автоматически по составу типа, а компилятор проверяет их в границах API вроде thread::spawn. Гарантий безопасности сам тип при этом не даёт — их даёт его устройство.

  6. #rs_threads6 / 9
    Почему thread::spawn(|| println!("{:?}", v)) не компилируется, а с move — да?
    A)Без move замыкание копирует v, а Vec копировать запрещено
    B)spawn принимает только замыкания без захвата переменных
    C)Замыкание переживёт функцию, заимствовать локальную v нельзя
    D)Компилятор требует move для всех замыканий с телом длиннее строки
    показать ответ и разбор
    +C)Замыкание переживёт функцию, заимствовать локальную v нельзя

    // разбор: Поток живёт сам по себе и может пережить функцию, которая его запустила, поэтому spawn требует от замыкания 'static: никаких ссылок на чужие локальные значения. move передаёт владение внутрь, и вопрос снимается. Если владение отдавать не хочется, берут thread::scope — там компилятор знает, что потоки завершатся до выхода.

  7. #rs_atomics7 / 9
    Чем Ordering::Relaxed отличается от SeqCst для простого счётчика?
    A)Relaxed может потерять инкремент при высокой конкуренции
    B)Relaxed работает только на одном ядре, SeqCst — на всех
    C)Relaxed откладывает запись до ближайшего барьера памяти
    D)Relaxed даёт атомарность, но не порядок соседних операций
    показать ответ и разбор
    +D)Relaxed даёт атомарность, но не порядок соседних операций

    // разбор: Атомарность у обоих одинаковая — инкремент не потеряется. Разница в видимости соседних операций: Relaxed не мешает компилятору и процессору переставлять другие обращения к памяти вокруг него. Для счётчика, чьё значение читают в конце, этого достаточно; для флага «данные готовы» нужны Release и Acquire.

  8. #rs_channels8 / 9
    Что вернёт recv(), когда все отправители уничтожены, а буфер пуст?
    A)Ok со значением по умолчанию для типа сообщения
    B)Заблокируется навсегда: получатель не знает о судьбе отправителей
    C)Панику: работа с закрытым каналом считается ошибкой
    D)Err: канал закрыт, ждать больше нечего
    показать ответ и разбор
    +D)Err: канал закрыт, ждать больше нечего

    // разбор: Канал знает, сколько живых Sender осталось: когда последний уничтожен и очередь опустела, recv возвращает Err(RecvError). Это штатный сигнал завершения — цикл for msg in rx просто заканчивается. Отсюда типовая ошибка: держать лишнюю копию отправителя в главном потоке, из-за чего получатель ждёт вечно.

  9. #rs_mutex9 / 9
    Когда снимается блокировка, взятая через m.lock().unwrap()?
    A)Когда guard уходит из области видимости — в его Drop
    B)В конце функции, где была взята блокировка
    C)После последнего обращения к данным внутри блокировки
    D)При явном вызове m.unlock() — иначе блокировка остаётся
    показать ответ и разбор
    +A)Когда guard уходит из области видимости — в его Drop

    // разбор: lock возвращает MutexGuard — RAII-обёртку: пока она жива, блокировка держится, её Drop освобождает мьютекс. Метода unlock нет, потому что он позволил бы обращаться к данным после освобождения. Чтобы сузить критическую секцию, guard кладут в отдельный блок или роняют явным drop(guard).

это 9 из 72

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

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

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