% Send и Sync
Не все подчиняется наследуемой изменяемости, однако. Некоторые типы позволяют
размножать совпадающие ссылки на место в памяти, и при этом изменять его. Если эти типы не
используют синхронизацию, чтобы управлять доступом к этой памяти, они абсолютно не
потокобезопасны. Rust отражает это через типажи Send
и Sync
.
- Тип является Send если его безопасно передавать другому потоку.
- Тип является Sync если его безопасно разделять между потоками (
&T
является Send).
Send и Sync - это основа многопоточности в Rust. Из-за этого существует большое количество специальных инструментов для того, чтобы заставить их работать правильно. Первый и самый главный - это то, что они являются небезопасными типажами. Это означает, что их реализация небезопасна, а другой небезопасный код подразумевает, что они реализованы корректно. Из-за того, что они являются маркерными типажами (у них нет объектов, связанных с ними, таких как методы), корректная реализация просто означает, что они обладают внутренними свойствами, которыми должна обладать реализация. Некорректная реализация Send или Sync может вызвать Неопределенное Поведение.
Send и Sync это к тому же автоматически наследуемые типажи. Это означает, что в отличие от любого другого типажа, если тип состоит только из типов Send или Sync, то он тоже Send или Sync. Почти все примитивные типы являются Send и Sync, и, как следствие, большинство типов, с которыми вы столкнетесь, тоже будут Send и Sync.
Главные исключения:
- сырые указатели не являются ни Send ни Sync (из-за того, что у них нет охранников безопасности).
UnsafeCell
не является Sync (и, следовательно,Cell
иRefCell
тоже).Rc
не является Send или Sync (потому что количество ссылок является общим и несинхронизированным ).
Rc
и UnsafeCell
фундаментально непотокобезопасны: они разрешают
несинхронизированное общее изменяемое состояние. Однако сырые указатели, строго
говоря, помечены непотокобезопасными в большинстве своем для статического
анализа. Для выполнения чего-нибудь полезного с сырыми указателями их
необходимо разыменовать, что уже небезопасно. В этом смысле, можно было бы сказать,
что их можно пометить потокобезопасными.
Но все же важно пометить их непотокобезопасными, чтобы запретить автоматическую
маркировку типов, их содержащих, потокобезопасными. У таких типов нетривиальная
передача владения, и, вряд ли, их автор обязательно сильно задумывался о
потокобезопасности. В случае с Rc, мы имеем замечательный пример типа,
содержащего *mut
, который точно непотокобезопасен.
Типы, для которых Send
и Sync
не выведены автоматически, могут легко реализовать их:
#![allow(unused)] fn main() { struct MyBox(*mut u8); unsafe impl Send for MyBox {} unsafe impl Sync for MyBox {} }
В чрезвычайно редких случаях у типов, которым не нужно автоматическое выведение Send или Sync, необходимо убрать реализацию Send и Sync:
#![allow(unused)] #![feature(optin_builtin_traits)] fn main() { // Я обладаю особой магической семантикой для некоторых примитивов синхронизации! struct SpecialThreadToken(u8); impl !Send for SpecialThreadToken {} impl !Sync for SpecialThreadToken {} }
Заметьте, что, само по себе, невозможно некорректно вывести Send и Sync. Только типы, которым другой небезопасный код приписывает особое значение, могут, вероятно, доставить проблемы при некорректном Send или Sync.
Большинство типов, использующих сырые указатели, должны быть инкапсулированы в
абстракцию, к которой можно вывести Send и Sync. Например, все стандартные
коллекции Rust являются Send и Sync (когда содержат типы Send и Sync), несмотря
на постоянное использование ими сырых указателей для управления размещением в
памяти и сложным владением. Аналогично, большинство итераторов по этим
коллекциям являются Send и Sync, потому что они во многом ведут себя как &
или
&mut
в коллекции.
TODO: лучше объяснить, что могут и что не могут Send или Sync. Достаточно ли обращений к гонкам данных?