Расширяемая конкурентность: Send и Sync
Интересно, что почти все средства конкурентности, о которых шла речь в этой главе, — часть стандартной библиотеки, а не языка. Ваши возможности не ограничены языком и стандартной библиотекой: можно писать собственные средства конкурентности или пользоваться написанными другими.
Однако среди ключевых понятий конкурентности есть и встроенные в сам язык, а не
в стандартную библиотеку, — это трейты Send и Sync из std::marker.
Передача владения между потоками
Трейт-маркер Send означает, что владение значениями типа, который его
реализует, можно передавать между потоками. Send реализуют почти все типы
Rust, но есть исключения, в том числе Rc<T>. Он не может реализовать Send:
если склонировать значение Rc<T> и передать владение клоном в другой поток,
оба потока могут одновременно изменить счётчик ссылок. Поэтому Rc<T>
предназначен для однопоточных ситуаций, где не хочется платить
производительностью за потокобезопасность.
Таким образом, система типов и ограничения трейтами в Rust гарантируют, что
значение Rc<T> нельзя случайно небезопасно отправить в другой поток. Когда мы
попытались сделать это в листинге 16-14, то получили ошибку
the trait `Send` is not implemented for `Rc<Mutex<i32>>`. А когда перешли
на Arc<T>, который Send реализует, код скомпилировался.
Любой тип, целиком состоящий из типов Send, автоматически тоже помечается как
Send. Почти все примитивные типы — Send, кроме сырых указателей, о которых
мы поговорим в главе 20.
Доступ из нескольких потоков
Трейт-маркер Sync означает, что на значение типа, который его реализует,
безопасно ссылаться из нескольких потоков. Иначе говоря, тип T реализует
Sync, если &T (неизменяемая ссылка на T) реализует Send — то есть ссылку
можно безопасно отправить в другой поток. Как и в случае с Send, все
примитивные типы реализуют Sync, а типы, целиком состоящие из типов с Sync,
тоже реализуют Sync.
Умный указатель Rc<T> не реализует и Sync — по тем же причинам, по которым
не реализует Send. Тип RefCell<T> (о нём мы говорили в главе 15) и
родственные типы семейства Cell<T> тоже не реализуют Sync: проверка
заимствований, которую RefCell<T> выполняет во время работы, не
потокобезопасна. А умный указатель Mutex<T> реализует Sync и годится для
общего доступа из нескольких потоков, как вы видели в разделе «Общий доступ к
Mutex<T>».
Ручная реализация Send и Sync небезопасна
Раз типы, целиком состоящие из других типов с Send и Sync, реализуют Send
и Sync автоматически, реализовывать эти трейты вручную не нужно. Как
трейты-маркеры, они даже не содержат методов. Они нужны лишь для того, чтобы
обеспечивать инварианты, связанные с конкурентностью.
Ручная реализация этих трейтов требует небезопасного кода на Rust. О
небезопасном Rust мы поговорим в главе 20; пока важно понимать, что создание
новых конкурентных типов, которые состоят не из частей с Send и Sync,
требует тщательного обдумывания, чтобы сохранить гарантии безопасности.
Подробнее об этих гарантиях и о том, как их соблюдать, — в «The
Rustonomicon».
Итоги
Это не последняя встреча с конкурентностью в книге: следующая глава посвящена асинхронному программированию, а проект из главы 21 применит понятия этой главы в ситуации, более приближенной к реальной, чем небольшие примеры здесь.
Как уже говорилось, в самом языке Rust очень мало средств конкурентности, поэтому многие решения реализованы в виде крейтов. Они развиваются быстрее стандартной библиотеки, так что обязательно поищите в интернете актуальные современные крейты для многопоточных задач.
Стандартная библиотека Rust даёт каналы для передачи сообщений и умные
указатели вроде Mutex<T> и Arc<T>, которые безопасно использовать в
конкурентном контексте. Система типов и анализатор заимствований гарантируют,
что в коде, использующем эти решения, не будет гонок данных и
недействительных ссылок. Как только код скомпилировался, можно быть спокойным:
он будет прекрасно работать в нескольких потоках — без трудноуловимых багов,
обычных для других языков. Конкурентное программирование больше не нужно
бояться: вперёд — делайте свои программы конкурентными без страха!