window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster już istnieje') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() ROZWIDLANIE I WĄTKOWANIE W RUBY - The Codest
The Codest
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Branże
    • Fintech i bankowość
    • E-commerce
    • Adtech
    • Healthtech
    • Produkcja
    • Logistyka
    • Motoryzacja
    • IOT
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
Strzałka w tył WSTECZ
2016-10-06
Software Development

ROZWIDLANIE I WĄTKOWANIE W RUBY

Marek Gierlach

Jak zapewne wiesz, Ruby ma kilka implementacji, takich jak MRI, JRuby, Rubinius, Opal, RubyMotion itp. i każda z nich może wykorzystywać inny wzorzec wykonywania kodu. W tym artykule skupimy się na pierwszych trzech z nich i porównamy MRI

Jak zapewne wiesz, Ruby ma kilka implementacji, takich jak MRI, JRuby, Rubinius, Opal, RubyMotion itp. i każda z nich może używać innego wzorca kod wykonanie. W tym artykule skupimy się na pierwszych trzech z nich i porównamy MRI (obecnie najpopularniejszą implementację) z JRuby i Rubinius, uruchamiając kilka przykładowych skryptów, które mają ocenić przydatność forkowania i wątkowania w różnych sytuacjach, takich jak przetwarzanie algorytmów wymagających dużej mocy obliczeniowej procesora, kopiowanie plików itp.

Widelec

  • jest nowym procesem potomnym (kopią procesu nadrzędnego)
  • ma nowy identyfikator procesu (PID)
  • ma oddzielną pamięć*
  • komunikuje się z innymi za pośrednictwem kanałów komunikacji międzyprocesowej (IPC), takich jak kolejki komunikatów, pliki, gniazda itp.
  • istnieje nawet po zakończeniu procesu nadrzędnego
  • jest wywołaniem POSIX - działa głównie na platformach uniksowych

Wątek

  • jest "tylko" kontekstem wykonania, działającym w ramach procesu
  • dzieli całą pamięć z innymi (domyślnie używa mniej pamięci niż fork)
  • komunikuje się z innymi za pomocą obiektów pamięci współdzielonej
  • umiera wraz z procesem
  • wprowadza typowe problemy związane z wielowątkowością, takie jak głód, zakleszczenia itp.

Istnieje wiele narzędzi wykorzystujących forki i wątki, które są używane na co dzień, np. Unicorn (forki) i Puma (wątki) na poziomie serwerów aplikacji, Resque (forki) i Sidekiq (wątki) na poziomie zadań w tle itp.

Poniższa tabela przedstawia wsparcie dla forków i wątków w głównych implementacjach Rubiego.

Implementacja RubyRozwidlenieGwintowanie
REZONANS MAGNETYCZNYTakTak (ograniczone przez GIL**)
JRuby–Tak
RubiniusTakTak

Dwa kolejne magiczne słowa powracają jak bumerang w tym temacie - równoległość i współbieżność - musimy je nieco wyjaśnić. Po pierwsze, terminy te nie mogą być używane zamiennie. W skrócie - o równoległości możemy mówić, gdy dwa lub więcej zadań jest przetwarzanych dokładnie w tym samym czasie. Współbieżność ma miejsce, gdy dwa lub więcej zadań jest przetwarzanych w nakładających się okresach czasu (niekoniecznie w tym samym czasie). Tak, to szerokie wyjaśnienie, ale wystarczająco dobre, aby pomóc ci zauważyć różnicę i zrozumieć resztę tego artykułu.

Raport Fronented na rok 2020

Poniższa tabela przedstawia obsługę równoległości i współbieżności.

Implementacja RubyRównoległość (poprzez rozwidlenia)Równoległość (poprzez wątki)Współbieżność
REZONANS MAGNETYCZNYTakNieTak
JRuby–TakTak
RubiniusTakTak (od wersji 2.X)Tak

To koniec teorii - zobaczmy to w praktyce!

  • Posiadanie oddzielnej pamięci nie musi powodować zużywania takiej samej ilości pamięci jak proces nadrzędny. Istnieje kilka technik optymalizacji pamięci. Jedną z nich jest Copy on Write (CoW), która pozwala procesowi nadrzędnemu współdzielić przydzieloną pamięć z procesem podrzędnym bez jej kopiowania. Dzięki CoW dodatkowa pamięć jest potrzebna tylko w przypadku modyfikacji pamięci współdzielonej przez proces potomny. W kontekście Rubiego nie każda implementacja jest przyjazna dla CoW, np. MRI wspiera ją w pełni od wersji 2.X. Przed tą wersją każdy fork zużywał tyle samo pamięci co proces nadrzędny.
  • Jedną z największych zalet/wad MRI (niepotrzebne skreślić) jest wykorzystanie mechanizmu GIL (Global Interpreter Lock). W skrócie, mechanizm ten odpowiada za synchronizację wykonywania wątków, co oznacza, że w danym momencie może być wykonywany tylko jeden wątek. Ale zaraz... Czy to oznacza, że nie ma sensu w ogóle używać wątków w MRI? Odpowiedź przychodzi wraz ze zrozumieniem wewnętrznych elementów GIL... lub przynajmniej spojrzeniem na próbki kodu w tym artykule.

Przypadek testowy

Aby zaprezentować jak działa forking i wątkowanie w implementacjach Rubiego, stworzyłem prostą klasę o nazwie Test i kilka innych dziedziczących po niej. Każda klasa ma inne zadanie do przetworzenia. Domyślnie każde zadanie wykonywane jest cztery razy w pętli. Ponadto, każde zadanie działa w trzech typach wykonywania kodu: sekwencyjnym, z rozwidleniami i z wątkami. Dodatkowo, Benchmark.bmbm uruchamia blok kodu dwukrotnie - pierwszy raz w celu uruchomienia środowiska uruchomieniowego, drugi raz w celu dokonania pomiarów. Wszystkie wyniki przedstawione w tym artykule zostały uzyskane w drugim uruchomieniu. Oczywiście, nawet bmbm nie gwarantuje doskonałej izolacji, ale różnice między wieloma uruchomieniami kodu są nieznaczne.

wymagają "benchmarku"

klasa Test
  KWOTA = 4

  def run
    Benchmark.bmbm do |b|
      b.report("sequential") { sequential }
      b.report("forking") { forking }
      b.report("threading") { threading }
    end
  end

  private

  def sequential
    AMOUNT.times { perform }
  end

  def forking
    AMOUNT.times do
      fork do
        wykonać
      end
    end

    Process.waitall
  rescue NotImplementedError => e
    Metoda fork # nie jest dostępna w JRuby
    puts e
  end

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        wykonać
      end
    end

    threads.map(&:join)
  end

  def perform
    raise "nie zaimplementowano"
  end
end
Test obciążenia

Wykonuje obliczenia w pętli, aby wygenerować duże obciążenie procesora.

class LoadTest < Test
  def perform
    1000.times { 1000.times { 2**3**4 } }
  end
end

Uruchommy to...

LoadTest.new.run

...i sprawdzić wyniki

REZONANS MAGNETYCZNYJRubyRubinius
sekwencyjny1.8629282.0890001.918873
rozwidlenie0.945018–1.178322
gwintowanie1.9139821.1070001.213315

Jak widać, wyniki z sekwencyjnych uruchomień są podobne. Oczywiście istnieje niewielka różnica między rozwiązaniami, ale jest ona spowodowana implementacją wybranych metod w różnych interpreterach.

Forking, w tym przykładzie, ma znaczny wzrost wydajności (kod działa prawie dwa razy szybciej).

Wątkowanie daje podobne wyniki jak rozwidlanie, ale tylko dla JRuby i Rubinius. Uruchomienie próbki z wątkami na MRI zużywa nieco więcej czasu niż metoda sekwencyjna. Istnieją co najmniej dwa powody. Po pierwsze, GIL wymusza sekwencyjne wykonywanie wątków, więc w idealnym świecie czas wykonania powinien być taki sam jak w przypadku sekwencyjnego uruchamiania, ale występuje również strata czasu na operacje GIL (przełączanie między wątkami itp.). Po drugie, potrzebny jest również pewien narzut czasowy na tworzenie wątków.

Ten przykład nie daje nam odpowiedzi na pytanie o sens używania wątków w MRI. Zobaczmy inny.

Test drzemki

Uruchamia metodę uśpienia.

class SnoozeTest < Test
  def perform
    sleep 1
  end
end

Oto wyniki

REZONANS MAGNETYCZNYJRubyRubinius
sekwencyjny4.0046204.0060004.003186
rozwidlenie1.022066–1.028381
gwintowanie1.0015481.0040001.003642

Jak widać, każda implementacja daje podobne wyniki nie tylko w uruchomieniach sekwencyjnych i forkowych, ale także w wątkowych. Dlaczego więc MRI ma taki sam wzrost wydajności jak JRuby i Rubinius? Odpowiedź tkwi w implementacji sen.

Rezonans magnetyczny sen jest zaimplementowana za pomocą rb_thread_wait_for C, która używa innej funkcji o nazwie native_sleep. Rzućmy okiem na jego implementację (kod został uproszczony, oryginalną implementację można znaleźć pod adresem tutaj):

static void
native_sleep(rb_thread_t *th, struct timeval *timeout_tv)
{
  ...

  GVL_UNLOCK_BEGIN();
  {
    // zrób kilka rzeczy tutaj
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

Powodem, dla którego ta funkcja jest ważna, jest to, że oprócz używania ścisłego kontekstu Ruby, przełącza się ona również do kontekstu systemowego, aby wykonać tam pewne operacje. W takich sytuacjach proces Ruby nie ma nic do roboty... Świetny przykład marnowania czasu? Nie do końca, ponieważ istnieje GIL mówiący: "Nic do roboty w tym wątku? Przełączmy się na inny i wróćmy tu po chwili". Można to zrobić, odblokowując i blokując GIL za pomocą GVL_UNLOCK_BEGIN() i GVL_UNLOCK_END() funkcje.

Sytuacja staje się jasna, ale sen jest rzadko przydatna. Potrzebujemy więcej przykładów z życia wziętych.

Test pobierania plików

Uruchamia proces, który pobiera i zapisuje plik.

wymagają "net/http"

class DownloadFileTest < Test
  def perform
    Net::HTTP.get("upload.wikimedia.org", "/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  end
end

Nie ma potrzeby komentowania poniższych wyników. Są one dość podobne do tych z powyższego przykładu.

1.003642JRubyRubinius
sekwencyjny0.3279800.3340000.329353
rozwidlenie0.104766–0.121054
gwintowanie0.0857890.0940000.088490

Innym dobrym przykładem może być proces kopiowania plików lub jakakolwiek inna operacja wejścia/wyjścia.

Wnioski

  • Rubinius w pełni obsługuje zarówno rozwidlanie, jak i wątkowanie (od wersji 2.X, kiedy usunięto GIL). Twój kod może być współbieżny i działać równolegle.
  • JRuby dobrze radzi sobie z wątkami, ale w ogóle nie obsługuje forkowania. Równoległość i współbieżność można osiągnąć za pomocą wątków.
  • REZONANS MAGNETYCZNY obsługuje rozwidlanie, ale wątkowanie jest ograniczone przez obecność GIL. Współbieżność można osiągnąć za pomocą wątków, ale tylko wtedy, gdy uruchomiony kod wykracza poza kontekst interpretera Ruby (np. operacje wejścia-wyjścia, funkcje jądra). Nie ma sposobu na osiągnięcie równoległości.

Powiązane artykuły

Software Development

Tworzenie przyszłościowych aplikacji internetowych: spostrzeżenia zespołu ekspertów The Codest

Odkryj, w jaki sposób The Codest wyróżnia się w tworzeniu skalowalnych, interaktywnych aplikacji internetowych przy użyciu najnowocześniejszych technologii, zapewniając płynne doświadczenia użytkowników na wszystkich platformach. Dowiedz się, w jaki sposób nasza wiedza napędza transformację cyfrową i biznes...

THEECODEST
Software Development

10 najlepszych firm tworzących oprogramowanie na Łotwie

Dowiedz się więcej o najlepszych łotewskich firmach programistycznych i ich innowacyjnych rozwiązaniach w naszym najnowszym artykule. Odkryj, w jaki sposób ci liderzy technologiczni mogą pomóc w rozwoju Twojej firmy.

thecodest
Rozwiązania dla przedsiębiorstw i scaleupów

Podstawy tworzenia oprogramowania Java: Przewodnik po skutecznym outsourcingu

Zapoznaj się z tym niezbędnym przewodnikiem na temat skutecznego tworzenia oprogramowania Java outsourcing, aby zwiększyć wydajność, uzyskać dostęp do wiedzy specjalistycznej i osiągnąć sukces projektu z The Codest.

thecodest
Software Development

Kompletny przewodnik po outsourcingu w Polsce

Wzrost liczby outsourcing w Polsce jest napędzany przez postęp gospodarczy, edukacyjny i technologiczny, sprzyjający rozwojowi IT i przyjazny klimat dla biznesu.

TheCodest
Rozwiązania dla przedsiębiorstw i scaleupów

Kompletny przewodnik po narzędziach i technikach audytu IT

Audyty IT zapewniają bezpieczne, wydajne i zgodne z przepisami systemy. Dowiedz się więcej o ich znaczeniu, czytając cały artykuł.

The Codest
Jakub Jakubowicz CTO & Współzałożyciel

Subskrybuj naszą bazę wiedzy i bądź na bieżąco!

    O nas

    The Codest - Międzynarodowa firma programistyczna z centrami technologicznymi w Polsce.

    Wielka Brytania - siedziba główna

    • Office 303B, 182-184 High Street North E6 2JA
      Londyn, Anglia

    Polska - lokalne centra technologiczne

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polska

      The Codest

    • Strona główna
    • O nas
    • Nasze Usługi
    • Case Studies
    • Nasze Know How
    • Kariera
    • Słownik

      Nasze Usługi

    • Konsultacje IT
    • Software Development
    • Backend Development
    • Frontend Development
    • Zespoły IT
    • Backend Dev
    • Inżynierowie rozwiązań chmurowych
    • Inżynierowie danych
    • Inne
    • Inżynierowie QA

      Raporty

    • Fakty i mity na temat współpracy z zewnętrznym partnerem programistycznym
    • Z USA do Europy: Dlaczego amerykańskie startupy decydują się na relokację do Europy?
    • Porównanie centrów rozwoju Tech Offshore: Tech Offshore Europa (Polska), ASEAN (Filipiny), Eurazja (Turcja)
    • Jakie są największe wyzwania CTO i CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Warunki korzystania z witryny

    Copyright © 2025 by The Codest. Wszelkie prawa zastrzeżone.

    pl_PLPolish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek pl_PLPolish