Розширювана конкурентність із трейтами Sync і Send

Цікаво, що мова Rust має дуже небагато функціоналу для конкурентності. Майже весь функціонал конкурентності, про який ми будемо говорити в цьому розділі, є частиною стандартної бібліотеки, а не мови. Ваші опції для роботи з конкурентністю не обмежуються мовою чи стандартною бібліотекою; ви можете написати власний функціонал для конкурентності або використовувати ті, що вже були написані іншими.

Однак, дві концепції конкурентності вбудовані в мову: трейти Sync and Send із std::marker.

Дозвіл передавати володіння між потоками за допомогою Send

Маркерний трейт Send підказує нам, що володіння значенням типу, який реалізує Send можна передавати між потоками. Майже кожен тип в Rust реалізує Send, але є деякі винятки, включаючи Rc<T>: він не може реалізовувати Send, оскільки якщо ви клонували значення Rc<T> і спробували передати володіння клоном в інший потік, обидва потоки могли оновити лічильник підрахунку посилань одночасно. З цієї причини Rc<T> реалізовано для використання в одному потоці, коли ви не хочете жертвувати ефективністю виконання коду.

Тому, система типів Rust і межі трейтів (trait bounds) гарантують, що ви ніколи не зможете випадково небезпечно надіслати значення Rc<T> між потоками. Коли ми спробували зробити це в Блоці коду 16-14, то отримали помилкуthe trait Send is not implemented for Rc<Mutex<i32>>. Коли ми почали використовувати Arc<T>, який реалізує Send, код скомпілювався.

Будь-який тип, який повністю складається з типів, що реалізують Send, також автоматично позначається як Send. Майже всі примітивні типи реалізують Send, окрім сирих вказівників (raw pointers), які ми обговоримо в Розділі 19.

Дозвіл доступу з кількох потоків за допомогою Sync

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

Розумний вказівник Rc<T> також не реалізує Sync з тих самих причин з яких не реалізує Send. Тип RefCell<T> (про який ми говорили в Розділі 15) і сімейство повʼязаних типів Cell<T> також не реалізують Sync. Реалізація перевірки запозичень (borrow checking), яку RefCell<T> виконує під час виконання програми, не є потокобезпечною. Розумний покажчик Mutex<T> реалізує Sync і може бути спільно використовуватись декількома потоками, як ви могли побачити в секції "Спільне використання Mutex<T> декількома потоками" секції

Реалізовувати Send і Sync вручну небезпечно

Оскільки типи, які складаються з типів, що реалізують Send і Sync, автоматично також реалізують Send і Sync, нам не потрібно реалізовувати ці трейти вручну. Як маркерні трейти, вони навіть не мають жодних методів, які потрібно реалізовувати. Вони просто корисні для забезпечення виконання інваріантів, пов’язаних із конкурентністю.

Ручне реалізація цих трейтів передбачає використання unsafe Rust коду. Ми поговоримо про використання unsafe Rust коду в Розділі 19; наразі ж, важливою інформацією є те, що для створення нових конкурентних типів, які не складаються з Send і Sync, потрібно ретельно продумати гарантії безпеки. “The Rustonomicon” містить більше інформації про такі гарантії та способи їх забезбечення.

Підсумок

Це не останній раз, коли ви читаєте про конкурентність у цій книзі: проєкт у Розділі 20 використовуватиме концепції цього розділу в більш реалістичній ситуації, ніж менші приклади, які тут розглядались.

Як вже згадувалося раніше, оскільки лише дуже маленька доля функціоналу для роботи з конкурентністю є частиною мови Rust, багато рішень реалізовано як крейти. Вони розвиваються швидше, ніж стандартна бібліотека, тому обов’язково шукайте в інтернеті поточні, найсучасніші крейти для використання при роботі з конкурентністю.

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

Далі ми поговоримо про ідіоматичні способи моделювання проблем і структурування рішень, по мірі того як ваші Rust програми стають більшими. Крім того, ми обговоримо, як ідіоми Rust пов’язані з ідіомами, що можуть бути вам знайомі з об’єктно-орієнтованого програмування. ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads