Расширенная многопоточность с помощью типажей Sync
и Send
Интересно, что сам язык Rust имеет очень мало возможностей для многопоточности. Почти все функции многопоточности о которых мы говорили в этой главе, были частью стандартной библиотеки, а не языка. Ваши варианты работы с многопоточностью не ограничиваются языком или стандартной библиотекой; Вы можете написать свой собственный многопоточный функционал или использовать возможности написанные другими.
Тем не менее, в язык встроены две концепции многопоточности: std::marker
типажи Sync
и Send
.
Разрешение передачи во владение между потоками с помощью Send
Маркерный типаж Send
указывает, что владение типом реализующим Send
, может передаваться между потоками. Почти каждый тип Rust является типом Send
, но есть некоторые исключения, вроде 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
, кроме сырых указателей, которые мы обсудим в главе 19.
Разрешение доступа из нескольких потоков с Sync
Маркерный типаж Sync
указывает, что на тип реализующий 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 в главе 19; на данный момент важная информация заключается в том, что для создания новых многопоточных типов, не состоящих из частей Send
и Sync
необходимо тщательно продумать гарантии безопасности. В Rustonomicon есть больше информации об этих гарантиях и о том как их соблюдать.
Итоги
Это не последний случай, когда вы увидите многопоточность в этой книге: проект в главе 20 будет использовать концепции этой главы для более реалистичного случая, чем небольшие примеры обсуждаемые здесь.
Как упоминалось ранее, поскольку в языке Rust очень мало того, с помощью чего можно управлять многопоточностью, многие решения реализованы в виде крейтов. Они развиваются быстрее, чем стандартная библиотека, поэтому обязательно поищите в Интернете текущие современные крейты.
Стандартная библиотека Rust предоставляет каналы для передачи сообщений и типы умных указателей, такие как Mutex<T>
и Arc<T>
, которые можно безопасно использовать в многопоточных контекстах. Система типов и анализатор заимствований гарантируют, что код использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив компилирующийся код, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является концепцией, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно!
Далее мы поговорим об идиоматичных способах моделирования проблем и структурирования решений по мере усложнения ваших программ на Rust. Кроме того, мы обсудим как идиомы Rust связаны с теми, с которыми вы, возможно, знакомы по объектно-ориентированному программированию.