Расширяемая конкурентность с 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>, которые безопасно использовать в конкурентных контекстах. Система типов и проверщик заимствований гарантируют, что код, использующий эти решения, не будет содержать гонок данных или недействительных ссылок. Как только ваш код скомпилируется, вы можете быть уверены, что он будет успешно работать в нескольких потоках без трудноуловимых ошибок, характерных для других языков. Конкурентное программирование больше не является концепцией, которой стоит бояться: вперёд, делайте свои программы конкурентными — бестрашно!