Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Расширяемая конкурентность с помощью типажей Send и Sync

Интересно, что почти все возможности конкурентности, о которых мы говорили до сих пор в этой главе, являются частью стандартной библиотеки, а не языка. Ваши варианты обработки конкурентности не ограничены языком или стандартной библиотекой; вы можете написать собственные возможности конкурентности или использовать те, что написаны другими.

Однако среди ключевых концепций конкурентности, встроенных в язык, а не в стандартную библиотеку, находятся маркер-типажи std::marker Send и Sync.

Разрешение передачи владения между потоками с помощью 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, за исключением сырых указателей, которые мы обсудим в Главе 20.

Разрешение доступа из нескольких потоков с помощью Sync

Маркер-типаж Sync указывает, что для типа, реализующего Sync, безопасно иметь ссылку из нескольких потоков. Другими словами, любой тип T реализует Sync, если &T (неизменяемая ссылка на T) реализует Send, что означает, что ссылку можно безопасно отправить в другой поток. Подобно Send, все примитивные типы реализуют Sync, и типы, состоящие полностью из типов, которые реализуют Sync, также реализуют Sync.

Sync — это наиболее близкое понятие в Rust к разговорному значению фразы “потокобезопасный” (thread-safe), т.е. что определённый фрагмент данных может безопасно использоваться несколькими параллельными потоками. Причина наличия отдельных типажей Send и Sync в том, что тип иногда может быть одним, или обоими, или ни одним. Например:

  • Умный указатель Rc<T> также не является ни Send, ни Sync по описанным выше причинам.
  • Тип RefCell<T> (о котором мы говорили в Главе 15) и семейство связанных типов Cell<T> являются Send (если T: Send), но они не являются Sync. RefCell можно отправить через границу потока, но не можно получить доступ параллельно, потому что реализация проверки заимствований, которую RefCell<T> выполняет во время выполнения, не является потокобезопасной.
  • Умный указатель Mutex<T> является Send и Sync и может использоваться для общего доступа с несколькими потоками, как вы видели в разделе “Общий доступ к Mutex<T> между несколькими потоками”.
  • Тип MutexGuard<'a, T>, который возвращается Mutex::lock, является Sync (если T: Sync), но не Send. Он именно не Send, потому что некоторые платформы требуют, чтобы мьютексы разблокировались тем же потоком, который их заблокировал.

Ручная реализация Send и Sync является небезопасной

Поскольку типы, состоящие полностью из других типов, которые реализуют типажи Send и Sync, также автоматически реализуют Send и Sync, нам не нужно реализовывать эти типажи вручную. Как маркер-типажи, у них даже нет никаких методов для реализации. Они просто полезны для обеспечения инвариантов, связанных с конкурентностью.

Ручная реализация этих типажей предполагает написание небезопасного кода Rust. Мы поговорим об использовании небезопасного кода Rust в Главе 20; пока что важная информация заключается в том, что создание новых конкурентных типов, не состоящих из частей Send и Sync, требует тщательного обдумывания для поддержания гарантий безопасности. В “Rustonomicon” есть более подробная информация об этих гарантиях и о том, как их поддерживать.

Резюме

Это не последний раз, когда вы увидите конкурентность в этой книге: следующая глава сосредоточена на асинхронном программировании, а проект в Главе 21 будет использовать концепции этой главы в более реалистичной ситуации, чем небольшие примеры, рассмотренные здесь.

Как упоминалось ранее, поскольку очень малое из того, как Rust обрабатывает конкурентность, является частью языка, многие решения конкурентности реализованы как крейты. Они развиваются быстрее, чем стандартная библиотека, поэтому обязательно ищите в интернете текущие, передовые крейты для использования в многопоточных ситуациях.

Стандартная библиотека Rust предоставляет каналы для передачи сообщений и типы умных указателей, такие как Mutex<T> и Arc<T>, которые безопасно использовать в конкурентных контекстах. Система типов и проверка заимствований гарантируют, что код, использующий эти решения, не закончится гонками данных или недействительными ссылками. Как только ваш код скомпилируется, вы можете быть уверены, что он будет успешно работать в нескольких потоках без тех трудноуловимых ошибок, которые характерны для других языков. Конкурентное программирование больше не концепция, которой стоит бояться: идите и делайте свои программы конкурентными, без страха!