Steuern und Kryptowährungen

Ruby verstehen: Ein tiefgehender Blick auf Threads, Parallelität und Nebenläufigkeit

Steuern und Kryptowährungen
Demystifying Ruby (1/3): It's all about threads

Erfahren Sie, wie Ruby mit Threads, Prozessen, Raktoren und Fibers umgeht und welche Auswirkungen dies auf die Parallelität und Nebenläufigkeit in Ruby-Programmen hat. Ein umfassender Leitfaden zur Entschlüsselung von Rubys gleichzeitigen Ausführungsmodellen und deren praktischen Einsatzmöglichkeiten.

Ruby ist eine dynamische, interpretierte und quelloffene Programmiersprache, die für ihre einfache und gut lesbare Syntax bekannt ist. Besonders im Bereich der Webentwicklung hat Ruby durch das Ruby on Rails Framework große Bedeutung erlangt. Neben seiner Flexibilität in der Programmierparadigma unterstützt Ruby objektorientierte, funktionale sowie imperative Ansätze. Doch wenn es um Threads, Nebenläufigkeit und Parallelität geht, stellen sich viele Entwickler die Frage: Wie parallel ist Ruby wirklich? Und welche Mechanismen bietet Ruby für gleichzeitige Ausführung? Um das zu verstehen, muss man die Facetten der Ruby-Implementierung kennen, vor allem des sogenannten Matz Ruby Interpreter (MRI), der meistgenutzten Ruby-Virtual-Machine, die von Yukihiro Matsumoto, dem Schöpfer von Ruby, entwickelt wurde. Ein zentrales Konzept, das MRI mit sich bringt, ist der Global Interpreter Lock, kurz GIL.

Dieser sorgt dafür, dass zu jedem Zeitpunkt nur ein Thread innerhalb des Ruby-Interpreters tatsächlich ausgeführt wird. Obwohl Ruby somit Mehrfach-Threads unterstützt, beschränkt der GIL die echte Parallelität. Das bedeutet, dass in der Praxis zwar mehrere Threads parallel laufen können, aber ihre Ausführung vom Interpreter seriell abgewickelt wird. In einfachen Worten hat Ruby durch den GIL nur eine begrenzte Parallelitätsfähigkeit auf Thread-Ebene, was insbesondere bei CPU-lastigen Anwendungen spürbar wird. Interessanterweise gibt es neben Threads noch weitere Ebenen der gleichzeitigen Ausführung in Ruby, die häufig übersehen werden.

Jede Ruby-Anwendung läuft in einer verschachtelten Umgebung: Zu jeder Zeit gibt es einen Prozess, darin laufen Raktoren (Ractor), darin wiederum Threads, und innerhalb der Threads laufen Fibers. Jeder dieser Bestandteile hat seine eigene Rolle im Bereich Parallelität und Nebenläufigkeit. Diese verborgene Struktur erklärt, warum man von Ruby oft hört, dass es „nebenläufig“ aber nur begrenzt „parallel“ ist. Der grundlegende Terminus, der außerhalb von Ruby auch häufig benutzt wird, ist der Prozess. Ein Prozess besitzt eigenen, isolierten Speicher und wird vom Betriebssystem verwaltet.

Dabei können mehrere Prozesse komplett unabhängig voneinander parallel auf verschiedenen CPU-Kernen laufen. Für Ruby bedeutet das: Wenn man mehrere Ruby-Programme in unterschiedlichen Prozessen startet, laufen sie parallel und getrennt voneinander. Innerhalb eines Prozesses ist allerdings die Speichertrennung strikt, was bedeutet, dass Datenaustausch zwischen Prozessen komplizierter ist und spezielle Kommunikationsmechanismen wie Pipes oder Sockets erfordert. Ruby bietet jedoch eine neuere, experimentelle Lösung, um echte Parallelität innerhalb eines Prozesses zu ermöglichen: Raktoren – benannt nach dem Actor-Modell, welches für verteilte und nebenläufige Systeme konzipiert wurde. Raktoren können als virtuelle Maschinen innerhalb eines Prozesses betrachtet werden, die jeweils eigene, isolierte Speicherbereiche haben.

Im Gegensatz zu Threads, die sich Speicher teilen, kommunizieren Raktoren ausschließlich über Nachrichten, was typische Probleme wie Race Conditions vermeidet. Durch ihre Isolation besitzen Raktoren jeweils einen eigenen GIL, wodurch sie theoretisch unabhängig voneinander und parallel laufen können. Der Nachteil ist, dass Raktoren aktuell noch experimentell sind und nicht in allen verwendeten Ruby-Gems etabliert sind. Ein praktisches Beispiel verdeutlicht die Leistungsfähigkeit der Parallelität mit Raktoren. Wird eine rechenintensive Aufgabe, zum Beispiel das Summieren von Zahlen in einem großen Bereich, in mehrere Teilaufgaben unterteilt und diese innerhalb von getrennten Raktoren ausgeführt, kann die Gesamtausführungszeit signifikant verbessert werden.

Während eine serielle Ausführung aller Teilbereiche entsprechend länger dauert, zeigt die parallele Bearbeitung mit Raktoren, dass die Arbeitslast auf die CPU-Kerne verteilt wird und somit echtes Parallelverhalten entsteht. Trotzdem sollte man sich der Limitationen bewusst sein, denn Raktoren sind noch nicht vollständig ausgereift und können unerwartete Schwierigkeiten in komplexen Anwendungen bereiten. Anders als Raktoren verwaltet MRI Ruby Threads intern selbst und versteckt dabei die tatsächliche Thread-Verwaltung des Betriebssystems. Das führt dazu, dass Ruby-Threads leichter und weniger ressourcenintensiv sind als native Betriebssystem-Threads, aber auch nicht wirklich parallel ausgeführt werden, sondern oft als sogenannte „Green Threads“ bezeichnet werden. Diese Eigenschaft macht Ruby-Threads besonders gut für Aufgaben geeignet, die I/O-gebunden sind, wie Datenbankabfragen oder Netzwerkanfragen, da der Interpreter während der Wartezeiten threadübergreifend wechseln kann und so die Anwendung reaktionsfähig bleibt.

In der Praxis zeigt ein simpler Vergleich die Vorteile von Threads bei I/O-lastigen Aufgaben: Werden zwei zeitaufwändige Aktionen nacheinander ausgeführt, dauert die Gesamtlaufzeit ungefähr doppelt so lange wie eine einzelne Aktion. Mit Threads hingegen starten beide Aktionen gleichzeitig und die gesamte Wartezeit entspricht in etwa einer einzelnen Aktion, da beide in gewisser Weise gleichzeitig verarbeitet werden. Für CPU-intensive Aufgaben bieten Ruby-Threads aufgrund des GIL jedoch wenig Vorteile. Eine große Herausforderung bei der Arbeit mit Threads ist das Teilen von Speicher und Ressourcen. Da Threads innerhalb desselben Prozesses existieren und denselben Speicherbereich verwenden, besteht die Gefahr von Race Conditions.

Diese treten auf, wenn mehrere Threads gleichzeitig auf gemeinsame Daten schreiben oder lesen und nicht angemessen synchronisiert sind. Ein anschauliches Beispiel ist ein gemeinsamer Zähler, der von mehreren Threads erhöht wird: Durch kleine Zeitverzögerungen und Kontextwechsel können Threads dieselbe Zähloperation mehrfach ausführen oder Werte überschreiben, sodass das Ergebnis am Ende inkorrekt ist. Daher sind sorgfältige Synchronisationsmechanismen wie Mutexes notwendig, um Race Conditions zu vermeiden, was den Programmieraufwand erhöht und Fehleranfälligkeit mit sich bringt. Neben Threads bietet Ruby eine noch feinere Ebene der Nebenläufigkeit: Fibers. Fibers sind leichtgewichtige, kooperative Einheiten, die innerhalb desselben Threads laufen.

Im Gegensatz zu Threads wechseln Fibers nicht automatisch durch den Interpreter, sondern müssen explizit die Kontrolle durch Aufrufe von Fiber.yield und Fiber.resume abgeben. Dies ermöglicht ein bewusstes Multitasking, das jedoch keine echte Parallelität bietet, da Fibers immer noch sequentiell auf einem Kernel-Thread ausgeführt werden. Fibers eignen sich besonders gut zur Implementierung von Generatoren oder für asynchrone Programmiermodelle, bei denen die Kontrolle sorgfältig zwischen verschiedenen Aufgaben übergeben wird.

Der wesentliche Vorteil liegt in der geringen Speicherbelegung und der Kontrolle über den Ausführungsfluss. Die Entwicklung von Fibers in Ruby gewann an Bedeutung durch Gem-Projekte wie Async, die asynchrone Programmierung mit Fibers abstrahieren und deutlich elegantere Nutzungsschnittstellen ermöglichen. Für reguläre Anwendungen sind Fibers jedoch eher ein Spezialwerkzeug und werden häufig nicht direkt vom Entwickler eingesetzt. Zusammenfassend zeigt sich, dass Ruby verschiedene Modelle für Nebenläufigkeit und Parallelität bereitstellt, die jeweils ihre Stärken und Schwächen haben. Prozesse bieten maximale Isolation und echte Parallelität über den Betriebssystemkern hinweg, sind aber speicher- und ressourcenintensiv.

Threads ermöglichen nebenläufige Ausführung innerhalb eines Prozesses, jedoch sind sie durch den Global Interpreter Lock limitiert und teilen sich Speicher, was Synchronisation erforderlich macht. Raktoren sind ein vielversprechender neuer Ansatz, um echte Parallelität mit isoliertem Speicher in demselben Prozess zu ermöglichen, sie befinden sich aber noch in der experimentellen Phase. Fibers runden das Bild als leichtgewichtige, kooperative Mechanismen für Nebenläufigkeit ab und eignen sich gut für reaktive und asynchrone Programme. Die Kenntnis dieser Konzepte ist für Ruby-Entwickler essenziell, um fundierte Entscheidungen im Umgang mit Nebenläufigkeit treffen zu können. Je nach Anwendungsfall – ob rechenintensive Tasks, I/O-gebundene Prozesse oder asynchrone Steuerungsflüsse – entscheidet sich, welche Technik sinnvoll ist.

Automatischer Handel mit Krypto-Geldbörsen Kaufen Sie Ihre Kryptowährung zum besten Preis

Als Nächstes
Apache Doris supercharges Cisco WebEx's data platform
Dienstag, 24. Juni 2025. Wie Apache Doris die Datenplattform von Cisco WebEx revolutioniert

Eine tiefgehende Analyse, wie Apache Doris die Datenverarbeitung und Analyse bei Cisco WebEx optimiert und dadurch eine leistungsstarke und skalierbare Datenplattform schafft.

DoorDash driver helped cheat company out of $2.5M using phantom deliveries
Dienstag, 24. Juni 2025. Wie ein DoorDash-Fahrer das Unternehmen mit Phantomlieferungen um 2,5 Millionen Dollar betrog

Ein aufsehenerregender Betrugsfall zeigt, wie ein DoorDash-Fahrer gemeinsam mit Komplizen und einem ehemaligen Mitarbeiter des Unternehmens eine ausgeklügelte Methode entwickelte, um das Unternehmen durch fingierte Bestellungen um Millionen zu bringen.

Multi-tenant Postgres can be easy
Dienstag, 24. Juni 2025. Multi-Tenant Postgres einfach gemacht: Effiziente Mehrmandantenfähigkeit für moderne B2B-Anwendungen

Erfahren Sie, wie die Mehrmandantenfähigkeit in PostgreSQL einfach und sicher umgesetzt werden kann. Der Fokus liegt auf praktischen Lösungen zur Datenisolation, Performanceoptimierung und Fehlervermeidung bei Multi-Tenant-Systemen in B2B-Anwendungen.

Show HN: Building an IntelliJ Code Generation Plugin with LLM
Dienstag, 24. Juni 2025. Effiziente Codegenerierung: Entwicklung eines IntelliJ-Plugins mit großen Sprachmodellen

Erfahren Sie, wie die Kombination von großen Sprachmodellen (LLM) und IntelliJ zu einem leistungsstarken Code-Generierungs-Plugin führt, das die Automatisierung von Programmieraufgaben, speziell für Java-Entwickler, revolutioniert und die Effizienz in Entwicklungsprojekten deutlich steigert.

Improving the Hill Climbing Algorithm (Pt-BR)
Dienstag, 24. Juni 2025. Optimierung des Hill-Climbing-Algorithmus am Beispiel des N-Damen-Problems

Erfahren Sie, wie der Hill-Climbing-Algorithmus verbessert werden kann, um das N-Damen-Problem effizienter zu lösen. Entdecken Sie verschiedene erprobte Ansätze zur Vermeidung lokaler Optima und erhalten Sie praxisnahe Einblicke in die Umsetzung und Performance verschiedener Lösungsstrategien.

Pioneer of the long-form graphic novel, Jack Katz, passes away at the age of 97
Dienstag, 24. Juni 2025. Jack Katz: Der Wegbereiter des langen Graphic Novels ist im Alter von 97 Jahren verstorben

Jack Katz, ein Pionier des langen Graphic Novels, hinterlässt mit seinem monumentalen Werk 'The First Kingdom' ein bahnbrechendes Vermächtnis im Bereich der Comic-Kunst. Sein Leben und Werk prägen die Graphic Novel Szene bis heute.

Dangerously high levels of arsenic and cadmium found in store-bought rice
Dienstag, 24. Juni 2025. Gefährlich hohe Arsen- und Cadmiumwerte in gekauften Reisprodukten: Gesundheitsrisiken und sichere Alternativen

Entdecken Sie die gesundheitlichen Risiken durch hohe Belastungen von Arsen und Cadmium in Reisprodukten aus dem Handel, erfahren Sie, welche Reissorten besonders belastet sind und wie Sie durch richtige Auswahl und Zubereitung Ihre Exposition senken können.