ПрограммированиеРазработчик системных библиотек / Backend разработчик

Как работает «безопасность многопоточности» (Send и Sync) в Rust, и как их можно реализовать/ограничить для пользовательских типов?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

В Rust безопасность при работе с потоками обеспечивается за счёт двух автоматических трейтов: Send и Sync.

  • Send позволяет передавать тип между потоками (путём передачи владения).
  • Sync гарантирует, что тип можно безопасно использовать из нескольких потоков одновременно (за счёт ссылок).

Большинство стандартных типов в Rust по умолчанию реализуют эти трейты. Например, Arc<T>, Mutex<T>Send и Sync (если T тоже соответствует этим трейтам).

Вы можете явно запретить или реализовать эти трейты для своих типов. Например, если у вас есть внутреннее небезопасное поле (например, голый указатель или иносторонние ресурсы), следует сделать типы !Send или !Sync:

use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> не реализует Send/Sync, поэтому MyType также не будет реализовать эти трейты.

Если вы реализуете низкоуровневую обёртку и ручное управление потокобезопасностью, можно реализовать Send/Sync вручную (unsafe):

unsafe impl Send for MyType {} unsafe impl Sync for MyType {}

Это ответственность программиста — дать гарантии потокобезопасности.

Вопрос с подвохом.

Что произойдёт, если вынести Rc<T> в несколько потоков через Arc<Rc<T>>?

Часто думают, что Arc всё защищает. Но Rc<T> не реализует Send/Sync, даже если обернуть в Arc! Вот так:

use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // Компилятор не даст это сделать! }

Arc не компенсирует отсутствие Send/Sync у вложенных членов.

Примеры реальных ошибок из-за незнания тонкостей темы.


История

В проекте была попытка использовать Arc<Rc<T>>, чтобы разделить данные между потоками и неблокирующим образом делить владение. Программа падала во время исполнения с непредсказуемым поведением; оказалось, что Rc не потокобезопасен, и этого изначально не хватило знаний о трейтах Send/Sync.


История

В self-made event loop тип State хранил сырой указатель на данные. Тип State пометили unsafe impl Send, но забыли расставить синхронизацию. В итоге возникла классическая data race, выявленная уже после релиза.


История

Разработчик реализовал newtype обёртку над Mutex, но ошибочно сделал её !Sync, не реализовав Sync вручную. Это мешало использовать тип в многопоточном контексте (например, внутри Arc<Mutex<T>>), т.к. компилятор требовал Sync. Исправлено реализацией unsafe impl Sync и анализом безопасности.