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