jak dowiedzieć się, co nie jest bezpieczne dla wątków w ruby?

Począwszy od Rails 4 , wszystko musiałoby działać domyślnie w środowisku wątkowym. Oznacza to cały kod, który piszemy i wszystkie klejnoty, których używamy, muszą być threadsafe

Mam kilka pytań na ten temat:
  1. co nie jest bezpieczne dla wątków w ruby / rails? Vs czym jest thread-safe w ruby / rails?
  2. czy istnieje lista klejnotów, które znane jako threadsafe lub vice-versa?
  3. czy jest lista wspólnych wzorców kodu, które nie są wątkami przykład @result ||= some_method?
  4. czy struktury danych w rdzeniu Ruby lang są takie jak Hash etc threadsafe?
  5. na rezonansie magnetycznym, gdzie GVL/GIL co oznacza, że tylko 1 Wątek ruby może działać na raz z wyjątkiem IO, czy zmiana threadsafe wpływa na nas?
Author: CuriousMind, 2013-03-03

3 answers

Żadna z podstawowych struktur danych nie jest bezpieczna dla wątków. Jedyne, co znam, to implementacja kolejki w bibliotece standardowej (require 'thread'; q = Queue.new).

GIL MRI nie ratuje nas od problemów z bezpieczeństwem wątku. Upewnia się tylko, że dwa wątki nie mogą uruchomić kodu Ruby w tym samym czasie, tzn. na dwóch różnych procesorach w tym samym czasie. Wątki mogą być zatrzymywane i wznawiane w dowolnym momencie kodu. Jeśli piszesz kod w stylu @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } } np. mutując współdzieloną zmienną z wielu wątków wartość zmiennej współdzielonej nie jest deterministyczna. Gil jest mniej więcej symulacją systemu pojedynczego rdzenia, nie zmienia podstawowych kwestii pisania poprawnych programów współbieżnych.

Nawet jeśli MRI był jednowątkowy jak węzeł.js trzeba by jeszcze pomyśleć o współbieżności. Przykład ze zmienną inkrementowaną będzie działał dobrze, ale nadal można uzyskać Warunki rasowe, w których rzeczy dzieją się w niedeterministycznym porządku i jeden oddzwanianie tłucze wynik innego. Jednowątkowe systemy asynchroniczne są łatwiejsze do zrozumienia, ale nie są wolne od problemów ze współbieżnością. Po prostu pomyśl o aplikacji z wieloma użytkownikami: jeśli dwóch użytkowników naciśnie edytuj na Postie przepełnienia stosu mniej więcej w tym samym czasie, poświęć trochę czasu na edycję postu, a następnie naciśnij Zapisz, którego zmiany będą widoczne przez trzeciego użytkownika później, gdy przeczytają ten sam post?

W Rubim, tak jak w większości innych jednoczesnych środowisk, wszystko, co jest bardziej niż jedna operacja nie jest bezpieczny wątek. @n += 1 nie jest bezpieczny dla wątku, ponieważ jest to wiele operacji. {[5] } czy wątek jest bezpieczny, ponieważ jest to jedna operacja(jest wiele operacji pod maską i prawdopodobnie wpadłbym w kłopoty, gdybym spróbował szczegółowo opisać, dlaczego jest to "wątek bezpieczny", ale w końcu nie uzyskasz niespójnych wyników z zadań). @n ||= 1, nie jest i żadna inna operacja skrótu + przypisanie też nie jest. Jeden błąd, który popełniłem wiele razy, to pisanie return unless @started; @started = true, które nie jest wątek w ogóle Bezpieczny.

Nie znam żadnej autorytatywnej listy bezpiecznych i niezwiązanych z wątkami instrukcji dla Rubiego, ale istnieje prosta zasada: jeśli wyrażenie wykonuje tylko jedną (wolną od efektów ubocznych) operację, to prawdopodobnie jest bezpieczne dla wątków. Na przykład: a + b is ok, a = b is also ok, and a.foo(b) is ok, if the method foo is side-effect free (ponieważ prawie wszystko w Rubim jest wywołaniem metody, nawet przypisanie w wielu przypadkach, odnosi się to również do innych przykładów). Skutki uboczne w tym kontekście oznaczają rzeczy, które zmieniają stan. def foo(x); @x = x; end jest Nie wolna od skutków ubocznych.

Jedną z najtrudniejszych rzeczy w pisaniu bezpiecznego kodu wątku w Rubim jest to, że wszystkie podstawowe struktury danych, w tym array, hash i string, są mutowalne. Bardzo łatwo jest przypadkowo ujawnić kawałek swojego stanu, a kiedy ten kawałek jest zmienny, rzeczy mogą się naprawdę spieprzyć. Rozważmy następujący kod:

class Thing
  attr_reader :stuff

  def initialize(initial_stuff)
    @stuff = initial_stuff
    @state_lock = Mutex.new
  end

  def add(item)
    @state_lock.synchronize do
      @stuff << item
    end
  end
end

Instancja tej klasy może być współdzielona między wątkami i mogą bezpiecznie dodawać do niego rzeczy, ale jest błąd współbieżności (nie jedyny): wewnętrzny stan obiektu przecieka przez accesor stuff. Poza tym, że jest problematyczny z perspektywy enkapsulacji, otwiera również puszkę robaków współbieżnych. Może ktoś bierze tę tablicę i przekazuje ją gdzie indziej, a ten kod z kolei myśli, że jest teraz właścicielem tej tablicy i może z nią robić, co chce.

Kolejny klasyczny przykład Rubiego jest taki:

STANDARD_OPTIONS = {:color => 'red', :count => 10}

def find_stuff
  @some_service.load_things('stuff', STANDARD_OPTIONS)
end

find_stuff działa dobrze za pierwszym razem, ale zwraca coś innego za drugim razem. Dlaczego? Metoda load_things myśli, że jest właścicielem przekazywanych do niej opcji i robi to color = options.delete(:color). Teraz STANDARD_OPTIONS stała nie ma już tej samej wartości. Stałe są stałe tylko w tym, do czego się odnoszą, nie gwarantują stałości struktur danych, do których się odnoszą. Pomyśl tylko, co by się stało, gdyby ten kod był uruchamiany jednocześnie.

Jeśli unikniesz współdzielonego mutowalnego stanu (np. instancja zmienne w obiektach, do których dostęp ma wiele wątków, struktury danych, takie jak hasze i tablice, do których dostęp ma wiele wątków) bezpieczeństwo wątków nie jest takie trudne. Spróbuj zminimalizować części aplikacji, do których dostęp jest możliwy jednocześnie, i skoncentruj swoje wysiłki na tym obszarze. IIRC, w aplikacji Rails, nowy obiekt kontrolera jest tworzony dla każdego żądania, więc będzie używany tylko przez pojedynczy wątek, i to samo dotyczy wszystkich obiektów modelu, które tworzysz z tego kontrolera. Jednak Rails zachęca również użycie zmiennych globalnych (User.find(...) używa zmiennej globalnej User, możesz myśleć o niej tylko jako o klasie i jest to klasa, ale jest to również przestrzeń nazw dla zmiennych globalnych), niektóre z nich są bezpieczne, ponieważ są tylko do odczytu, ale czasami zapisujesz rzeczy w tych zmiennych globalnych, ponieważ jest to wygodne. Bądź bardzo ostrożny, gdy używasz czegokolwiek, co jest dostępne globalnie.

Od dłuższego czasu możliwe jest uruchamianie szyn w środowiskach gwintowanych, więc bez bycia Railami ekspercie posunąłbym się nawet do stwierdzenia, że nie trzeba się martwić o bezpieczeństwo gwintów, jeśli chodzi o same szyny. Możesz nadal tworzyć aplikacje Rails, które nie są bezpieczne dla wątków, wykonując niektóre z rzeczy, o których wspomniałem powyżej. Jeśli chodzi o inne klejnoty, Załóżmy, że nie są bezpieczne, chyba że mówią, że są, a jeśli mówią, że są, Załóżmy, że nie są, i przejrzyj ich kod (ale tylko dlatego, że widzisz, że idą rzeczy jak @n ||= 1 nie oznacza, że nie są thread safe, jest to całkowicie legalna rzecz do zrobienia we właściwym kontekście -- powinieneś zamiast tego szukać rzeczy takich jak mutable state w zmiennych globalnych, jak radzi sobie ze zmiennymi obiektami przekazywanymi do jego metod, a zwłaszcza jak radzi sobie z hashami opcji).

Wreszcie, bycie wątkiem niebezpiecznym jest właściwością przechodnią. Wszystko, co używa czegoś, co nie jest bezpieczne dla wątków, samo w sobie nie jest bezpieczne dla wątków.

 111
Author: Theo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-16 13:02:32

Oprócz odpowiedzi Theo, dodałbym kilka obszarów problemowych do poszukiwania w Rails szczególnie, jeśli przełączysz się na config.threadsafe!

  • Zmienne klasy :

    @@i_exist_across_threads

  • ENV :

    ENV['DONT_CHANGE_ME']

  • Wątki :

    Thread.start

 10
Author: crizCraig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-17 20:11:48

Zaczynając od Rails 4, wszystko musiałoby działać domyślnie w środowisku threaded

To nie jest w 100% poprawne. Szyny Thread-safe są domyślnie włączone. Jeśli wdrożysz na wieloprocesowym serwerze aplikacji, takim jak Passenger (community) lub Unicorn, nie będzie żadnej różnicy. Ta zmiana dotyczy tylko użytkowników, którzy wdrażają ją w środowisku wielowątkowym, takim jak Puma lub Passenger Enterprise > 4.0

W przeszłości, jeśli chcesz wdrożyć w aplikacji wielowątkowej serwer trzeba było włączyć config.threadsafe , który jest teraz domyślny, ponieważ wszystko, co zrobił, albo nie miało żadnych efektów, albo zostało zastosowane do aplikacji Rails działającej w jednym procesie (Prooflink ).

Ale jeśli chcesz wszystkie zalety rails 4 streaming i inne rzeczy w czasie rzeczywistym z wielowątkowego wdrażania to może znajdziesz Ten artykuł ciekawy. @ Theo sad, dla aplikacji Rails, faktycznie wystarczy pominąć mutujący stan statyczny podczas Prośba. Chociaż jest to prosta praktyka do naśladowania, niestety nie możesz być pewien tego dla każdego klejnotu, który znajdziesz. O ile pamiętam Charles Oliver Nutter z projektu JRuby miał kilka wskazówek na ten temat w ten podcast.

I jeśli chcesz napisać czyste równoległe Programowanie Ruby, gdzie potrzebujesz struktur danych, do których dostęp ma więcej niż jeden wątek, być może znajdziesz thread_safe Gem przydatny.

 9
Author: dre-hh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-25 14:51:14