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