Rozszerzalna współbieżność dzięki cechom Send i Sync
Co ciekawe, prawie każda funkcja współbieżności, o której do tej pory mówiliśmy w tym rozdziale, była częścią biblioteki standardowej, a nie języka. Twoje opcje obsługi współbieżności nie ograniczają się do języka ani biblioteki standardowej; możesz pisać własne funkcje współbieżności lub korzystać z tych napisanych przez innych.
Jednakże, wśród kluczowych koncepcji współbieżności, które są osadzone w języku,
a nie w bibliotece standardowej, znajdują się cechy znacznika std::marker
Send i Sync.
Przenoszenie własności między wątkami
Cecha znacznikowa Send wskazuje, że własność wartości typu implementującego
Send może być przenoszona między wątkami. Prawie każdy typ Rust
implementuje Send, ale istnieją pewne wyjątki, w tym Rc<T>: Nie może on
implementować Send, ponieważ gdybyś sklonował wartość Rc<T> i próbował
przenieść własność klonu do innego wątku, oba wątki mogłyby jednocześnie
zaktualizować licznik referencji. Z tego powodu Rc<T> jest zaimplementowany
do użytku w sytuacjach jednowątkowych, gdzie nie chcesz ponosić kary
wydajnościowej związanej z bezpieczeństwem wątków.
Dlatego system typów Rust i ograniczenia cech zapewniają, że nigdy nie możesz
przypadkowo wysłać wartości Rc<T> między wątkami w sposób niebezpieczny.
Kiedy próbowaliśmy to zrobić w Listingu 16-14, otrzymaliśmy błąd the trait `Send` is not implemented for `Rc<Mutex<i32>>`. Kiedy zmieniliśmy na Arc<T>,
który implementuje Send, kod się skompilował.
Każdy typ składający się wyłącznie z typów Send jest automatycznie
oznaczany jako Send. Prawie wszystkie typy prymitywne są Send, poza
surowymi wskaźnikami, o których będziemy rozmawiać w Rozdziale 20.
Dostęp z wielu wątków
Cecha znacznikowa Sync wskazuje, że bezpieczne jest odwoływanie się do typu
implementującego Sync z wielu wątków. Innymi słowy, każdy typ T
implementuje Sync, jeśli &T (niemutowalna referencja do T) implementuje
Send, co oznacza, że referencja może być bezpiecznie wysłana do innego wątku.
Podobnie jak Send, wszystkie typy prymitywne implementują Sync, a typy
składające się wyłącznie z typów implementujących Sync również implementują
Sync.
Wskaźnik sprytny Rc<T> również nie implementuje Sync z tych samych powodów,
dla których nie implementuje Send. Typ RefCell<T> (o którym mówiliśmy w
Rozdziale 15) i rodzina powiązanych typów Cell<T> nie implementują Sync.
Implementacja sprawdzania pożyczeń, którą RefCell<T> wykonuje w czasie
wykonania, nie jest bezpieczna wątkowo. Wskaźnik sprytny Mutex<T> implementuje
Sync i może być używany do współdzielenia dostępu z wieloma wątkami, jak
widziałeś w sekcji „Współdzielony dostęp do Mutex<T>”.
Ręczna implementacja Send i Sync jest niebezpieczna
Ponieważ typy składające się wyłącznie z innych typów, które implementują
cechy Send i Sync, automatycznie implementują również Send i Sync, nie
musimy implementować tych cech ręcznie. Jako cechy znacznikowe, nie mają one
nawet żadnych metod do zaimplementowania. Są po prostu przydatne do
egzekwowania niezmienników związanych ze współbieżnością.
Ręczna implementacja tych cech wiąże się z użyciem niebezpiecznego kodu Rust.
O używaniu niebezpiecznego kodu Rust będziemy rozmawiać w Rozdziale 20;
na razie ważną informacją jest to, że budowanie nowych typów współbieżnych nie
składających się z części Send i Sync wymaga starannego przemyślenia w
c celu utrzymania gwarancji bezpieczeństwa. „The Rustonomicon”
zawiera więcej informacji na temat tych gwarancji i sposobów ich
podtrzymywania.
Podsumowanie
To nie jest ostatni raz, kiedy spotkasz się ze współbieżnością w tej książce: następny rozdział skupia się na programowaniu asynchronicznym, a projekt w Rozdziale 21 wykorzysta koncepcje z tego rozdziału w bardziej realistycznej sytuacji niż omówione tutaj mniejsze przykłady.
Jak wspomniano wcześniej, ponieważ niewiele z tego, jak Rust obsługuje współbieżność, jest częścią języka, wiele rozwiązań współbieżności jest implementowanych jako crate’y. Ewoluują one szybciej niż biblioteka standardowa, więc pamiętaj, aby szukać w Internecie aktualnych, najnowocześniejszych crate’ów do użycia w sytuacjach wielowątkowych.
Biblioteka standardowa Rust zapewnia kanały do przekazywania wiadomości oraz
typy wskaźników sprytnych, takie jak Mutex<T> i Arc<T>, które są bezpieczne
w użyciu w kontekstach współbieżnych. System typów i sprawdzający pożyczki
zapewniają, że kod używający tych rozwiązań nie doprowadzi do wyścigów danych
oraz do nieważnych referencji. Gdy kod się skompiluje, możesz być pewien, że
będzie działał na wielu wątkach bez trudnych do znalezienia błędów, typowych
dla innych języków. Programowanie współbieżne nie jest już pojęciem, którego
należy się obawiać: idź i spraw, aby twoje programy były współbieżne,
bez strachu!