programowanieProgramista systemowy/backend i Embedded

Opowiedz o zasadach pracy z globalnymi zmiennymi (static) w Rust. Jak zapewniana jest bezpieczeństwo przy równoczesnym dostępie i czym różnią się różne metody inicjalizacji i synchronizacji?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Rust globalne zmienne deklarowane są za pomocą słowa kluczowego static. Takie zmienne żyją przez cały czas wykonywania programu. Domyślnie dostęp do niezmiennych static jest bezpieczny, ale do zmiennych mutowalnych użycie unsafe jest konieczne, ponieważ kompilator nie może zagwarantować braku wyścigów wątków.

Aby zapewnić bezpieczny dostęp do zmiennych globalnych, stosuje się specjalne typy opakowujące: Mutex, RwLock, typy Atomic* lub zewnętrzne crate (lazy_static, once_cell). Inicjalizacja jest "leniwa", jeśli wymagane jest opóźnione tworzenie obiektu przy pierwszym dostępie.

Przykłady:

static COUNTER: AtomicUsize = AtomicUsize::new(0); static ref CONFIG: Config = read_config(); // z użyciem lazy_static lub once_cell

Pytanie z podstępem

Czy można zadeklarować zmienną globalną mutowalną bez specjalnych typów, jeśli będzie używana tylko z jednego wątku? Czy trzeba pisać unsafe?

Odpowiedź: Kompilator zażąda użycia unsafe przy jakiejkolwiek zmianie zmiennej globalnej, nawet jeśli logika jest jednowątkowa. To ogólny wymóg dla wszystkich static mut. Tylko jeśli zmienna jest opakowana w strukturę synchronizującą (Mutex, atomik itp.), kompilator zezwoli na bezpieczny dostęp.

static mut VALUE: i32 = 0; unsafe { VALUE += 1; }

Przykłady prawdziwych błędów wynikających z nieznajomości niuansów tematu


Historia

W usłudze internetowej podczas inicjalizacji globalnej pamięci podręcznej użyto zwykłej zmiennej static ze stringiem. Równocześnie pisało do niej kilka wątków, co prowadziło do uszkodzeń pamięci i zrzutów aplikacji z powodu wyścigu wątków.


Historia

W narzędziu zespołowym na Rust zmieniano zmienną statyczną bez synchronizacji, ponieważ "działa tylko przez main". Później kod zmodyfikowano pod kątem wielowątkowości, zapominając o globalnym stanie mutowalnym, co spowodowało trudne do odnalezienia błędy.


Historia

W programie embedded niepoprawnie zainicjalizowano static CONFIG przez funkcję przy starcie, ale nie zapewniono, że zostanie ona wywołana dokładnie raz. W rezultacie niektóre części kodu odwoływały się do nieinicjalizowanej konfiguracji (null reference).