Unterschied zwischen einer "Coroutine" und einem "Thread"?

Antworten:

122

Coroutinen sind eine Form der sequentiellen Verarbeitung: Es wird jeweils nur eine ausgeführt (genau wie die Subroutinen AKA-Prozeduren AKA-Funktionen - sie geben den Staffelstab nur flüssiger untereinander weiter).

Threads sind (zumindest konzeptionell) eine Form der gleichzeitigen Verarbeitung: Es können mehrere Threads gleichzeitig ausgeführt werden. (Traditionell wurde auf Single-CPU- und Single-Core-Computern diese Parallelität mit Hilfe des Betriebssystems simuliert. Da heutzutage so viele Computer Multi-CPU- und / oder Multi-Core-Computer sind, werden Threads de facto gleichzeitig ausgeführt. nicht nur "konzeptionell").

Alex Martelli
quelle
187

Lesen Sie zuerst: Parallelität vs. Parallelität - Was ist der Unterschied?

Parallelität ist die Trennung von Aufgaben, um eine verschachtelte Ausführung bereitzustellen. Parallelität ist die gleichzeitige Ausführung mehrerer Arbeiten, um die Geschwindigkeit zu erhöhen. - https://github.com/servo/servo/wiki/Design

Kurze Antwort: Bei Threads wechselt das Betriebssystem die laufenden Threads präventiv gemäß seinem Scheduler, einem Algorithmus im Betriebssystemkern. Bei Coroutinen bestimmen der Programmierer und die Programmiersprache, wann die Coroutinen gewechselt werden müssen. Mit anderen Worten, Aufgaben werden kooperativ multitasking ausgeführt, indem Funktionen an Sollwerten angehalten und wieder aufgenommen werden, typischerweise (aber nicht unbedingt) innerhalb eines einzelnen Threads.

Lange Antwort: Im Gegensatz zu Threads, die vom Betriebssystem vorbeugend geplant werden, sind Coroutine-Switches kooperativ, dh der Programmierer (und möglicherweise die Programmiersprache und ihre Laufzeit) steuert, wann ein Switch durchgeführt wird.

Im Gegensatz zu vorbeugenden Threads sind Coroutine-Switches kooperativ (der Programmierer steuert, wann ein Switch stattfinden wird). Der Kernel ist nicht an den Coroutine-Switches beteiligt. - http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html

Eine Sprache, die native Threads unterstützt, kann ihre Threads (Benutzer-Threads) auf den Threads des Betriebssystems ( Kernel-Threads ) ausführen . Jeder Prozess hat mindestens einen Kernel-Thread. Kernel-Threads sind wie Prozesse, außer dass sie den Speicherplatz in ihrem eigenen Prozess mit allen anderen Threads in diesem Prozess teilen. Ein Prozess "besitzt" alle zugewiesenen Ressourcen wie Speicher, Dateihandles, Sockets, Gerätehandles usw., und diese Ressourcen werden alle von seinen Kernel-Threads gemeinsam genutzt.

Der Betriebssystem-Scheduler ist Teil des Kernels, der jeden Thread für eine bestimmte Zeit (auf einem einzelnen Prozessorcomputer) ausführt. Der Scheduler weist jedem Thread Zeit (Timeslicing) zu. Wenn der Thread nicht innerhalb dieser Zeit fertig ist, wird er vom Scheduler vorab aktiviert (unterbricht ihn und wechselt zu einem anderen Thread). Auf einem Multiprozessor-Computer können mehrere Threads parallel ausgeführt werden, da jeder Thread auf einem separaten Prozessor geplant werden kann (aber nicht muss).

Auf einem Computer mit einem einzelnen Prozessor werden Threads schnell in Zeitscheiben geschnitten und vorbelegt (zwischen ihnen gewechselt) (unter Linux beträgt die Standardzeitscheibe 100 ms), wodurch sie gleichzeitig ausgeführt werden. Sie können jedoch nicht parallel (gleichzeitig) ausgeführt werden, da ein Single-Core-Prozessor jeweils nur eine Sache ausführen kann.

Coroutinen und / oder Generatoren können verwendet werden, um kooperative Funktionen zu implementieren. Anstatt auf Kernel-Threads ausgeführt und vom Betriebssystem geplant zu werden, werden sie in einem einzelnen Thread ausgeführt, bis sie nachgeben oder beendet werden, was anderen vom Programmierer festgelegten Funktionen entspricht. Sprachen mit Generatoren wie Python und ECMAScript 6 können zum Erstellen von Coroutinen verwendet werden. Async / await (zu sehen in C #, Python, ECMAscript 7, Rust) ist eine Abstraktion, die auf Generatorfunktionen basiert, die Futures / Versprechen liefern.

In einigen Kontexten können sich Coroutinen auf stapelbare Funktionen beziehen, während sich Generatoren auf stapellose Funktionen beziehen können.

Fasern , leichte Fäden und grüne Fäden sind andere Namen für Coroutinen oder coroutinenähnliche Dinge. Sie sehen manchmal (normalerweise absichtlich) eher wie Betriebssystem-Threads in der Programmiersprache aus, werden jedoch nicht wie echte Threads parallel ausgeführt und funktionieren stattdessen wie Coroutinen. (Je nach Sprache oder Implementierung können zwischen diesen Konzepten spezifischere technische Besonderheiten oder Unterschiede bestehen.)

Zum Beispiel hatte Java " grüne Fäden "; Dies waren Threads, die von der Java Virtual Machine (JVM) anstatt nativ auf den Kernel-Threads des zugrunde liegenden Betriebssystems geplant wurden. Diese liefen nicht parallel oder nutzten mehrere Prozessoren / Kerne - da dies einen nativen Thread erfordern würde! Da sie nicht vom Betriebssystem geplant wurden, waren sie eher Coroutinen als Kernel-Threads. Grüne Threads werden von Java verwendet, bis native Threads in Java 1.2 eingeführt wurden.

Threads verbrauchen Ressourcen. In der JVM hat jeder Thread seinen eigenen Stapel, normalerweise 1 MB groß. 64 KB ist der geringste zulässige Stapelspeicherplatz pro Thread in der JVM. Die Thread-Stapelgröße kann in der Befehlszeile für die JVM konfiguriert werden. Trotz des Namens sind Threads nicht frei, da sie Ressourcen wie jeden Thread benötigen, der einen eigenen Stapel benötigt, einen Thread-lokalen Speicher (falls vorhanden) und die Kosten für Thread-Planung / Kontextwechsel / Ungültigmachung des CPU-Cache. Dies ist einer der Gründe, warum Coroutinen für leistungskritische, hochkonkurrierende Anwendungen populär geworden sind.

Unter Mac OS kann ein Prozess nur etwa 2000 Threads zuweisen, und Linux weist 8 Thread pro Thread zu und erlaubt nur so viele Threads, die in den physischen RAM passen.

Daher sind Threads das schwerste Gewicht (in Bezug auf Speichernutzung und Kontextwechselzeit), dann Coroutinen und schließlich Generatoren das geringste Gewicht.

llambda
quelle
2
+1, aber diese Antwort könnte von einigen Referenzen profitieren.
Kojiro
1
Grüne Fäden sind etwas anderes als Coroutinen. sind sie nicht? Sogar Fasern haben einige Unterschiede. siehe programmers.stackexchange.com/questions/254140/…
112

Ungefähr 7 Jahre zu spät, aber den Antworten hier fehlt ein Zusammenhang zwischen Co-Routinen und Threads. Warum erhalten Coroutinen in letzter Zeit so viel Aufmerksamkeit und wann würde ich sie im Vergleich zu Threads verwenden ?

Wenn Coroutinen gleichzeitig (niemals parallel ) ausgeführt werden, warum sollte jemand sie Threads vorziehen?

Die Antwort ist, dass Coroutinen ein sehr hohes Maß an Parallelität mit sehr geringem Overhead bieten können . Im Allgemeinen haben Sie in einer Thread-Umgebung höchstens 30-50 Threads, bevor der Aufwand für die tatsächliche Planung dieser Threads (durch den Systemplaner) die Zeit, die die Threads tatsächlich für nützliche Arbeit leisten, erheblich verkürzt.

Ok, mit Threads können Sie Parallelität haben, aber nicht zu viel Parallelität. Ist das nicht immer noch besser als eine Co-Routine, die in einem einzelnen Thread ausgeführt wird? Naja nicht unbedingt. Denken Sie daran, dass eine Co-Routine immer noch Parallelität ohne Scheduler-Overhead ausführen kann - sie verwaltet einfach die Kontextumschaltung selbst.

Wenn Sie beispielsweise eine Routine haben, die einige Arbeiten ausführt und eine Operation ausführt, von der Sie wissen, dass sie für einige Zeit blockiert wird (z. B. eine Netzwerkanforderung), können Sie mit einer Co-Routine sofort zu einer anderen Routine wechseln, ohne den Systemplaner in sich aufnehmen zu müssen diese Entscheidung - ja, Sie als Programmierer müssen angeben, wann Co-Routinen wechseln können.

Mit vielen Routinen, die sehr wenig Arbeit erledigen und freiwillig untereinander wechseln, haben Sie ein Effizienzniveau erreicht, auf das kein Planer jemals hoffen kann. Sie können jetzt Tausende von Coroutinen zusammenarbeiten lassen, im Gegensatz zu Dutzenden von Threads.

Da Ihre Routinen jetzt einen festgelegten Punkt untereinander wechseln, können Sie jetzt auch das Sperren von gemeinsam genutzten Datenstrukturen vermeiden (da Sie Ihren Code niemals anweisen würden, mitten in einem kritischen Abschnitt zu einer anderen Coroutine zu wechseln).

Ein weiterer Vorteil ist die viel geringere Speichernutzung. Beim Thread-Modell muss jeder Thread seinen eigenen Stapel zuweisen, sodass Ihre Speichernutzung linear mit der Anzahl der vorhandenen Threads wächst. Bei Co-Routinen steht die Anzahl der Routinen nicht in direktem Zusammenhang mit Ihrer Speichernutzung.

Und schließlich erhalten Co-Routinen viel Aufmerksamkeit, da in einigen Programmiersprachen (wie Python) Ihre Threads ohnehin nicht parallel ausgeführt werden können - sie werden gleichzeitig wie Coroutinen ausgeführt, jedoch ohne geringen Arbeitsspeicher und freien Planungsaufwand.

Martin Konecny
quelle
2
Wie kann in Coroutinen zu einer anderen Aufgabe gewechselt werden, wenn eine Blockierungsoperation auftritt?
Narcisse Doudieu Siewe
Wenn Sie zu einer anderen Aufgabe wechseln, müssen alle Blockierungsvorgänge tatsächlich asynchron ausgeführt werden. Dies bedeutet, dass Sie die Verwendung von Operationen vermeiden müssen, die tatsächlich blockieren würden, und nur Operationen verwenden müssen, die das Nichtblockieren unterstützen, wenn sie in Ihrem Coroutine-System verwendet werden. Die einzige Möglichkeit, dies zu umgehen, besteht darin, dass Coroutinen vom Kernel unterstützt werden, z. B. UMS unter Windows, wo sie in Ihren Scheduler springen, wenn Ihr UMS-Thread einen Systemaufruf blockiert.
Retep998
@MartinKonecny ​​Entspricht der aktuelle C ++ - Threads TS dem von Ihnen erwähnten Ansatz?
Nikos
Eine moderne Programmiersprache würde also letztendlich beide Coroutinen / Fasern benötigen, um einen einzelnen CPU-Kern effizient zu nutzen, z. B. für nicht rechenintensive Operationen wie E / A und Threads, um CPU-intensive Operationen auf vielen Kernen zu parallelisieren, um an Geschwindigkeit zu gewinnen, oder?
Mahatma_Fatal_Error
19

Mit einem Wort: Vorkaufsrecht. Coroutinen verhalten sich wie Jongleure, die sich immer wieder gut eingespielte Punkte geben. Threads (echte Threads) können an fast jedem Punkt unterbrochen und später wieder aufgenommen werden. Dies bringt natürlich alle möglichen Probleme mit Ressourcenkonflikten mit sich, daher Pythons berüchtigtes GIL - Global Interpreter Lock.

Viele Thread-Implementierungen ähneln eher Coroutinen.

Peter Rowell
quelle
9

Dies hängt von der verwendeten Sprache ab. Zum Beispiel sind sie in Lua dasselbe (der variable Typ einer Coroutine heißt thread).

Obwohl Coroutinen freiwilliges Nachgeben implementieren, entscheiden (Sie) normalerweise, wo (Sie) der Programmierer entscheidet, wo er yieldeine andere Routine steuern soll.

Threads werden stattdessen automatisch vom Betriebssystem verwaltet (gestoppt und gestartet) und können sogar gleichzeitig auf Multicore-CPUs ausgeführt werden.

Thomas Bonini
quelle
0

12 Jahre zu spät zur Diskussion, aber eine Coroutine hat die Erklärung im Namen. Coroutine kann in Co und Routine zerlegt werden.

Eine Routine ist in diesem Zusammenhang nur eine Folge von Operationen / Aktionen, und durch Ausführen / Verarbeiten einer Routine wird die Folge von Operationen einzeln in genau derselben Reihenfolge wie angegeben ausgeführt.

Co steht für Kooperation. Eine Co-Routine wird gebeten (oder besser erwartet), ihre Ausführung bereitwillig auszusetzen, um anderen Co-Routinen die Möglichkeit zu geben, ebenfalls ausgeführt zu werden. Bei einer Co-Routine geht es also darum, CPU-Ressourcen (bereitwillig) gemeinsam zu nutzen, damit andere dieselbe Ressource verwenden können, die sie selbst verwenden.

Ein Thread hingegen muss seine Ausführung nicht anhalten. Das Suspendieren ist für den Thread vollständig transparent und der Thread wird von der zugrunde liegenden Hardware gezwungen, sich selbst zu suspendieren. Dies geschieht auch so, dass es für den Thread größtenteils transparent ist, da es nicht benachrichtigt wird und sein Status nicht geändert, sondern gespeichert und später wiederhergestellt wird, wenn der Thread fortgesetzt werden darf.

Eine Sache, die nicht stimmt, ist, dass Co-Routinen nicht gleichzeitig ausgeführt werden können und keine Rennbedingungen auftreten können. Dies hängt von dem System ab, auf dem die Co-Routinen ausgeführt werden, und es ist einfach möglich, Co-Routinen abzubilden.

Es spielt keine Rolle, wie sich die Co-Routinen selbst aussetzen. Zurück in Windows 3.1 wurde int 03 in alle Programme eingewebt (oder musste dort platziert werden) und in C # fügen wir Ausbeute hinzu.

Martin Kersten
quelle