window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = Fenster if (w.LeadBooster) { console.warn('LeadBooster existiert bereits') } 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 }) }, } } })() FORKING UND THREADING IN RUBY - The Codest
Der Codest
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Branchen
    • Fintech & Bankwesen
    • E-commerce
    • Adtech
    • Gesundheitstechnik
    • Herstellung
    • Logistik
    • Automobilindustrie
    • IOT
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
Pfeil zurück ZURÜCK
2016-10-06
Software-Entwicklung

FORKING UND THREADING IN RUBY

Marek Gierlach

Wie Sie wahrscheinlich wissen, gibt es für Ruby einige Implementierungen wie MRI, JRuby, Rubinius, Opal, RubyMotion usw., und jede von ihnen kann ein anderes Muster der Codeausführung verwenden. Dieser Artikel konzentriert sich auf die ersten drei von ihnen und vergleicht MRI

Wie Sie wahrscheinlich wissen, gibt es für Ruby einige Implementierungen, wie z. B. MRI, JRuby, Rubinius, Opal, RubyMotion usw., und jede von ihnen kann ein anderes Muster von Code Ausführung. Dieser Artikel konzentriert sich auf die ersten drei von ihnen und vergleicht MRI (die derzeit populärste Implementierung) mit JRuby und Rubinius, indem er einige Beispielskripte ausführt, die die Eignung von Forking und Threading in verschiedenen Situationen bewerten sollen, z. B. bei der Verarbeitung von CPU-intensiven Algorithmen, beim Kopieren von Dateien usw. Bevor Sie mit dem "Learning by Doing" beginnen, müssen Sie ein paar grundlegende Begriffe überarbeiten.

Gabel

  • ist ein neuer untergeordneter Prozess (eine Kopie des übergeordneten Prozesses)
  • hat einen neuen Prozessbezeichner (PID)
  • hat einen separaten Speicher*
  • kommuniziert mit anderen über Interprozesskommunikationskanäle (IPC) wie Nachrichtenwarteschlangen, Dateien, Sockets usw.
  • besteht auch dann, wenn der übergeordnete Prozess endet
  • ist ein POSIX-Aufruf - funktioniert hauptsächlich auf Unix-Plattformen

Thema

  • ist "nur" ein Ausführungskontext, der innerhalb eines Prozesses arbeitet
  • teilt den gesamten Speicher mit anderen (standardmäßig wird weniger Speicher als bei einem Fork verwendet)
  • kommuniziert mit anderen über gemeinsame Speicherobjekte
  • stirbt mit einem Prozess
  • führt zu typischen Multi-Threading-Problemen wie Starvation, Deadlocks usw.

Es gibt eine Vielzahl von Tools, die Forks und Threads verwenden und täglich im Einsatz sind, z. B. Unicorn (Forks) und Puma (Threads) auf der Ebene der Anwendungsserver, Resque (Forks) und Sidekiq (Threads) auf der Ebene der Hintergrundaufgaben, usw.

Die folgende Tabelle zeigt die Unterstützung für Forking und Threading in den wichtigsten Ruby-Implementierungen.

Ruby-ImplementierungForkingEinfädeln
MRIJaJa (begrenzt durch GIL**)
JRuby–Ja
RubiniusJaJa

Zwei weitere Zauberwörter tauchen in diesem Thema wie ein Bumerang auf - Parallelität und Gleichzeitigkeit - wir müssen sie ein wenig erklären. Zunächst einmal können diese Begriffe nicht austauschbar verwendet werden. Kurz gesagt - wir können von Parallelität sprechen, wenn zwei oder mehr Aufgaben genau zur gleichen Zeit bearbeitet werden. Von Gleichzeitigkeit spricht man, wenn zwei oder mehr Aufgaben in sich überschneidenden Zeiträumen (nicht notwendigerweise zur gleichen Zeit) bearbeitet werden. Ja, das ist eine grobe Erklärung, aber gut genug, um Ihnen den Unterschied zu verdeutlichen und den Rest dieses Artikels zu verstehen.

Vorläufiger Bericht für 2020

Die folgende Tabelle zeigt die Unterstützung für Parallelität und Gleichzeitigkeit.

Ruby-ImplementierungParallelität (über Forks)Parallelität (über Threads)Gleichzeitigkeit
MRIJaNeinJa
JRuby–JaJa
RubiniusJaJa (seit Version 2.X)Ja

Das ist das Ende der Theorie - sehen wir es uns in der Praxis an!

  • Ein separater Speicher muss nicht unbedingt die gleiche Menge wie der übergeordnete Prozess beanspruchen. Es gibt einige Techniken zur Speicheroptimierung. Eine davon ist Copy on Write (CoW), die es dem Elternprozess ermöglicht, zugewiesenen Speicher mit dem Kindprozess zu teilen, ohne ihn zu kopieren. Mit CoW wird zusätzlicher Speicher nur dann benötigt, wenn ein Kindprozess Änderungen am gemeinsamen Speicher vornimmt. Im Ruby-Kontext ist nicht jede Implementierung CoW-freundlich, z.B. unterstützt MRI es seit der Version 2.X. Vor dieser Version verbrauchte jeder Fork genauso viel Speicher wie ein Elternprozess.
  • Einer der größten Vorteile/Nachteile von MRI (streichen Sie die unpassende Alternative) ist die Verwendung von GIL (Global Interpreter Lock). Kurz gesagt ist dieser Mechanismus für die Synchronisierung der Ausführung von Threads verantwortlich, was bedeutet, dass nur ein Thread zur gleichen Zeit ausgeführt werden kann. Aber Moment mal... Bedeutet das, dass es überhaupt keinen Sinn macht, Threads in MRI zu verwenden? Die Antwort kommt mit dem Verständnis der GIL-Interna... oder zumindest mit einem Blick auf die Codebeispiele in diesem Artikel.

Testfall

Um zu zeigen, wie Forking und Threading in den Implementierungen von Ruby funktionieren, habe ich eine einfache Klasse namens Test und ein paar andere, die von ihr erben. Jede Klasse hat eine andere Aufgabe zu bearbeiten. Standardmäßig wird jede Aufgabe viermal in einer Schleife ausgeführt. Außerdem läuft jede Aufgabe gegen drei Arten der Codeausführung: sequentiell, mit Forks und mit Threads. Hinzu kommt, Benchmark.bmbm führt den Codeblock zweimal aus - das erste Mal, um die Laufzeitumgebung zum Laufen zu bringen, das zweite Mal, um zu messen. Alle in diesem Artikel vorgestellten Ergebnisse wurden beim zweiten Durchlauf erzielt. Natürlich, auch bmbm Methode garantiert zwar keine perfekte Isolierung, aber die Unterschiede zwischen mehreren Codeläufen sind unbedeutend.

erfordern "Benchmark"

Klasse Test
  BETRAG = 4

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

  privat

  def sequentiell
    AMOUNT.times { perform }
  end

  def forking
    AMOUNT.times do
      fork do
        durchführen
      end
    end

    Process.waitall
  rescue NotImplementedError => e
    # fork-Methode ist in JRuby nicht verfügbar
    setzt e
  end

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        ausführen.
      end
    end

    threads.map(&:join)
  end

  def durchführen
    raise "nicht implementiert"
  end
end
Belastungstest

Führt Berechnungen in einer Schleife aus, um eine hohe CPU-Last zu erzeugen.

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

Lassen wir es laufen...

LoadTest.new.run

...und überprüfen Sie die Ergebnisse

MRIJRubyRubinius
sequenziell1.8629282.0890001.918873
Verzweigung0.945018–1.178322
Einfädeln1.9139821.1070001.213315

Wie Sie sehen können, sind die Ergebnisse der sequentiellen Läufe ähnlich. Natürlich gibt es einen kleinen Unterschied zwischen den Lösungen, der aber durch die zugrunde liegende Implementierung der gewählten Methoden in den verschiedenen Interpretern verursacht wird.

Das Forking bringt in diesem Beispiel einen erheblichen Leistungsgewinn (der Code läuft fast doppelt so schnell).

Threading führt zu ähnlichen Ergebnissen wie Forking, allerdings nur für JRuby und Rubinius. Die Ausführung des Beispiels mit Threads im MRT verbraucht etwas mehr Zeit als die sequenzielle Methode. Dafür gibt es mindestens zwei Gründe. Erstens erzwingt GIL die sequentielle Ausführung von Threads. In einer perfekten Welt sollte die Ausführungszeit die gleiche sein wie bei der sequentiellen Ausführung, aber es entsteht auch ein Zeitverlust für GIL-Operationen (Umschalten zwischen Threads usw.). Zweitens wird auch ein gewisser Overhead für die Erstellung von Threads benötigt.

Dieses Beispiel gibt uns keine Antwort auf die Frage nach dem Sinn von Anwendungsfäden im MRT. Schauen wir uns ein anderes Beispiel an.

Snooze-Test

Führt eine Schlafmethode aus.

class SnoozeTest < Test
  def perform
    sleep 1
  end
end

Hier sind die Ergebnisse

MRIJRubyRubinius
sequenziell4.0046204.0060004.003186
Verzweigung1.022066–1.028381
Einfädeln1.0015481.0040001.003642

Wie Sie sehen können, liefert jede Implementierung ähnliche Ergebnisse nicht nur bei den sequentiellen und Forking-Läufen, sondern auch bei den Threading-Läufen. Warum also hat MRI den gleichen Leistungszuwachs wie JRuby und Rubinius? Die Antwort liegt in der Implementierung von schlafen.

MRTs schlafen Methode ist implementiert mit rb_thread_wait_for C-Funktion, die eine andere Funktion namens nativer_schlaf. Werfen wir einen kurzen Blick auf die Implementierung (der Code wurde vereinfacht, die ursprüngliche Implementierung finden Sie unter hier):

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

  GVL_UNLOCK_BEGIN();
  {
    // hier einige Dinge tun
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

Der Grund, warum diese Funktion so wichtig ist, liegt darin, dass sie nicht nur den strikten Ruby-Kontext verwendet, sondern auch auf den Systemkontext umschaltet, um dort einige Operationen durchzuführen. In solchen Situationen hat der Ruby-Prozess nichts zu tun... Ein gutes Beispiel für Zeitverschwendung? Nicht wirklich, denn es gibt eine GIL, die sagt: "In diesem Thread gibt es nichts zu tun? Wechseln wir in einen anderen und kommen wir nach einer Weile hierher zurück". Dies könnte durch Entsperren und Sperren der GIL mit GVL_UNLOCK_BEGIN() und GVL_UNLOCK_END() Funktionen.

Die Situation wird klar, aber schlafen Methode ist selten nützlich. Wir brauchen mehr Beispiele aus der Praxis.

Test zum Herunterladen von Dateien

Führt einen Prozess zum Herunterladen und Speichern einer Datei aus.

erfordern "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

Die folgenden Ergebnisse brauchen nicht kommentiert zu werden. Sie sind denen des obigen Beispiels sehr ähnlich.

1.003642JRubyRubinius
sequenziell0.3279800.3340000.329353
Verzweigung0.104766–0.121054
Einfädeln0.0857890.0940000.088490

Ein weiteres gutes Beispiel ist der Kopiervorgang einer Datei oder eine andere E/A-Operation.

Schlussfolgerungen

  • Rubinius unterstützt sowohl Forking als auch Threading vollständig (seit Version 2.X, als GIL entfernt wurde). Ihr Code könnte nebenläufig sein und parallel laufen.
  • JRuby leistet gute Arbeit mit Threads, unterstützt aber überhaupt kein Forking. Parallelität und Gleichzeitigkeit können mit Threads erreicht werden.
  • MRI unterstützt Forking, aber Threading ist durch das Vorhandensein von GIL eingeschränkt. Gleichzeitigkeit kann mit Threads erreicht werden, aber nur, wenn laufender Code außerhalb des Ruby-Interpreter-Kontextes stattfindet (z.B. IO-Operationen, Kernel-Funktionen). Es gibt keine Möglichkeit, Parallelität zu erreichen.

Ähnliche Artikel

Software-Entwicklung

Zukunftssichere Web-Apps bauen: Einblicke vom The Codest-Expertenteam

Entdecken Sie, wie sich The Codest bei der Erstellung skalierbarer, interaktiver Webanwendungen mit Spitzentechnologien auszeichnet, die nahtlose Benutzererfahrungen auf allen Plattformen bieten. Erfahren Sie, wie unsere Expertise die digitale Transformation und...

DAS SCHÖNSTE
Software-Entwicklung

Top 10 Softwareentwicklungsunternehmen in Lettland

Erfahren Sie in unserem neuesten Artikel mehr über die besten Softwareentwicklungsunternehmen Lettlands und ihre innovativen Lösungen. Entdecken Sie, wie diese Technologieführer Ihr Unternehmen voranbringen können.

thecodest
Enterprise & Scaleups Lösungen

Grundlagen der Java-Softwareentwicklung: Ein Leitfaden für erfolgreiches Outsourcing

Entdecken Sie diesen wichtigen Leitfaden zum erfolgreichen Outsourcing der Java-Softwareentwicklung, um die Effizienz zu steigern, auf Fachwissen zuzugreifen und den Projekterfolg mit The Codest voranzutreiben.

thecodest
Software-Entwicklung

Der ultimative Leitfaden für Outsourcing in Polen

Der Anstieg des Outsourcings in Polen wird durch wirtschaftliche, bildungspolitische und technologische Fortschritte angetrieben, die das IT-Wachstum und ein unternehmensfreundliches Klima fördern.

TheCodest
Enterprise & Scaleups Lösungen

Der vollständige Leitfaden für IT-Audit-Tools und -Techniken

IT-Audits gewährleisten sichere, effiziente und gesetzeskonforme Systeme. Erfahren Sie mehr über ihre Bedeutung, indem Sie den vollständigen Artikel lesen.

Der Codest
Jakub Jakubowicz CTO & Mitbegründer

Abonnieren Sie unsere Wissensdatenbank und bleiben Sie auf dem Laufenden über das Fachwissen aus dem IT-Sektor.

    Über uns

    The Codest - Internationales Software-Unternehmen mit technischen Zentren in Polen.

    Vereinigtes Königreich - Hauptsitz

    • Büro 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale Tech-Hubs

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

      Der Codest

    • Startseite
    • Über uns
    • Dienstleistungen
    • Fallstudien
    • Gewusst wie
    • Karriere
    • Wörterbuch

      Dienstleistungen

    • IT-Beratung
    • Software-Entwicklung
    • Backend-Softwareentwicklung
    • Frontend-Softwareentwicklung
    • Staff Augmentation
    • Backend-Entwickler
    • Cloud-Ingenieure
    • Daten-Ingenieure
    • Andere
    • QS-Ingenieure

      Ressourcen

    • Fakten und Mythen über die Zusammenarbeit mit einem externen Softwareentwicklungspartner
    • Aus den USA nach Europa: Warum entscheiden sich amerikanische Start-ups für eine Verlagerung nach Europa?
    • Tech Offshore Development Hubs im Vergleich: Tech Offshore Europa (Polen), ASEAN (Philippinen), Eurasien (Türkei)
    • Was sind die größten Herausforderungen für CTOs und CIOs?
    • Der Codest
    • Der Codest
    • Der Codest
    • Privacy policy
    • Website terms of use

    Urheberrecht © 2025 von The Codest. Alle Rechte vorbehalten.

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