Розширювана конкурентність без страху (fearless concurrency) з Send і Sync
Цікаво, що майже кожна функція конкурентності, про яку ми говорили дотепер у цій главі, була частиною стандартної бібліотеки, а не мови. Ваші варіанти для обробки конкурентності не обмежуються мовою або стандартною бібліотекою; ви можете написати власні функції конкурентності або використовувати ті, що написані іншими.
Однак серед ключових концепцій конкурентності, які вбудовані в мову
замість стандартної бібліотеки, є трейти std::marker Send і Sync.
Передача власності між потоками
Маркований трейт Send вказує, що власність значень типу,
який реалізує Send, може бути передана між потоками. Майже кожен тип Rust
реалізує Send, але є деякі винятки, зокрема Rc<T>: він
не може реалізовувати Send, тому що якщо ви клонували б значення Rc<T> і спробували б
передати власність клону іншому потоку, обидва потоки могли б оновити
лічильник посилань одночасно. З цієї причини Rc<T> реалізовано
для використання в однопотокових ситуаціях, де ви не хочете сплачувати
штраф за продуктивність безпечності потоків.
Отже, система типів Rust і обмеження трейта гарантують, що ви ніколи
випадково не надішлете значення Rc<T> між потоками небезпечно. Коли ми спробували зробити
це у Listing 16-14, ми отримали помилку the trait `Send` is not implemented for `Rc<Mutex<i32>>`. Коли ми перейшли до Arc<T>, який реалізує Send, код скомпілювався.
Будь-який тип, повністю складений із типів Send, автоматично позначається як Send також. Майже всі примітивні типи є Send, за винятком сирих вказівників, про які
ми поговоримо в Chapter 20.
Доступ із кількох потоків
Маркований трейт Sync вказує, що безпечно, коли на тип, який реалізує
Sync, є посилання з кількох потоків. Іншими словами, будь-який тип T
реалізує Sync, якщо &T (незмінне посилання на T) реалізує Send,
тобто посилання можна безпечно надіслати іншому потоку. Подібно до Send,
усі примітивні типи реалізують Sync, і типи, повністю складені з типів, які
реалізують Sync, також реалізують Sync.
Розумний вказівник Rc<T> також не реалізує Sync з тих самих причин,
що й не реалізує Send. Тип RefCell<T> (про який ми говорили
в Chapter 15) і сімейство пов’язаних типів Cell<T> не реалізують
Sync. Реалізація перевірки запозичень, яку RefCell<T> виконує під час виконання,
не є потокобезпечною. Розумний вказівник Mutex<T> реалізує Sync і може бути
використаний для спільного доступу з кількома потоками, як ви бачили в “Shared Access to
Mutex<T>”.
Ручна реалізація Send і Sync є небезпечною
Оскільки типи, повністю складені з інших типів, які реалізують трейти Send і
Sync, також автоматично реалізують Send і Sync, нам не потрібно
реалізовувати ці трейти вручну. Як марковані трейти, вони навіть не мають жодних
методів для реалізації. Вони просто корисні для забезпечення інваріантів, пов’язаних із
конкурентністю.
Ручна реалізація цих трейтів передбачає реалізацію небезпечного коду Rust.
Ми поговоримо про використання небезпечного коду Rust у Chapter 20; наразі важлива
інформація полягає в тому, що побудова нових конкурентних типів, які не складаються з
частин Send і Sync, вимагає уважного обмірковування, щоб зберегти гарантії безпеки. “The
Rustonomicon” містить більше інформації про ці гарантії та про те, як їх
зберігати.
Підсумок
Це не останнє, що ви побачите про конкурентність у цій книзі: наступна глава зосереджується на async-програмуванні, а проєкт у Chapter 21 використовуватиме концепції з цієї глави в більш реалістичній ситуації, ніж менші приклади, обговорені тут.
Як згадувалося раніше, оскільки дуже мало з того, як Rust обробляє конкурентність, є частиною мови, багато рішень для конкурентності реалізовано як крейти. Вони розвиваються швидше, ніж стандартна бібліотека, тож обов’язково шукайте в інтернеті актуальні, найкращі крейти для використання в багатопотокових ситуаціях.
Стандартна бібліотека Rust надає канали для передавання повідомлень і типи
розумних вказівників, такі як Mutex<T> і Arc<T>, які безпечно використовувати в
конкурентних контекстах. Система типів і перевірник запозичень
гарантують, що код, який використовує ці рішення, не матиме станів гонки даних або некоректних посилань.
Коли ви змусите ваш код скомпілюватися, ви можете бути впевнені, що він із задоволенням
працюватиме на кількох потоках без тих видів складних для виявлення помилок, які є поширеними в
інших мовах. Конкурентне програмування більше не є концепцією, якої треба боятися:
Ідіть уперед і робіть ваші програми конкурентними, без страху!