С вопросом безопасной работы в многопоточных средах программисты сталкивались давно, постоянно сталкиваясь с проблемами гонок, неконсистентных данных и утечек памяти. В Rust был реализован уникальный подход с marker-trait'ами Send и Sync, чтобы эти проблемы минимизировать уже на этапе компиляции.
Проблема — отсутствие контроля доступа к разделяемым данным между потоками, приводящее к трудноотлавливаемым ошибкам. Во многих языках ответственность полностью на программисте, в Rust компилятор сам проверяет, что можно передавать/разделять между потоками.
Решение: трейт Send гарантирует возможность безопасной передачи объекта из одного потока в другой. Sync — возможность совместного доступа к ссылке на объект из разных потоков. Почти все стандартные типы в Rust автоматически реализуют эти трейты, а кастомные могут реализовать их вручную или запрещать через impl !Send или impl !Sync для специфических случаев.
Пример кода:
use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } // counter всегда будет равен 10 без гонок!
Ключевые особенности:
Может ли тип с небезопасными указателями быть Send или Sync?
Нет, если тип содержит raw pointer или ресурсы без гарантий потокобезопасности, он не реализует эти трейты, или разработчик должен реализовать их вручную с полной ответственностью (обычно с unsafe impl Send/Sync).
Являются ли Rc<T> и RefCell<T> Send или Sync?
Нет, Rc<T> и RefCell<T> небезопасны для многопоточного использования (ні Send, ні Sync). Для многопоточных сценариев используются Arc<T> и Mutex/ RwLock.
Что произойдет, если static переменная содержит тип без реализованного Sync?
Rust не позволит такой static переменной существовать: она должна быть Sync, иначе компилятор выдаст ошибку.
Молодой разработчик помещает объект Rc в thread::spawn — код компилируется только если Rc не передаётся между потоками. При попытке вынести Rc из thread::spawn получается компиляционная ошибка, т.к. Rc не реализует Send, и не защищён от гонок.
Плюсы:
Минусы:
Используется Arc+Mutex для многопоточного счётчика, все потоки работают с одними и теми же данными через потокобезопасный интерфейс. Нет гонок, код безопасен и устойчив.
Плюсы:
Минусы: