it-swarm.com.de

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

Was sind die Unterschiede zwischen einer "Koroutine" und einem "Faden"?

129
jldupont

Coroutinen sind eine Form der sequentiellen Verarbeitung: Es wird jeweils nur eine ausgeführt (genau wie bei Unterprogrammen, die AKA-Funktionen ausführen - sie geben den Taktstock flüssiger untereinander weiter).

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

96
Alex Martelli

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

Parallelität ist die Aufgabentrennung, um eine verschachtelte Ausführung zu ermöglichen. 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, der ein Algorithmus im Betriebssystemkern ist. Bei Coroutinen bestimmen der Programmierer und die Programmiersprache, wann die Coroutinen zu wechseln sind. Mit anderen Worten, Aufgaben werden kooperativ multitasking ausgeführt, indem Funktionen an festgelegten Punkten angehalten und wieder aufgenommen werden, typischerweise (aber nicht notwendigerweise) innerhalb eines einzelnen Threads.

Lange Antwort: Im Gegensatz zu Threads, die vom Betriebssystem vorab geplant werden, sind Coroutine-Switches kooperativ, dh der Programmierer (und möglicherweise die Programmierung) Sprache und ihre Laufzeit) steuert, wann ein Wechsel stattfinden wird.

Im Gegensatz zu vorbeugenden Threads sind Coroutine-Switches kooperativ (der Programmierer steuert, wann ein Switch ausgeführt 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 (Benutzerthreads) auf den Kernel-Threads des Betriebssystems ausführen ). Jeder Prozess hat mindestens einen Kernel-Thread. Kernel-Threads sind mit Prozessen vergleichbar, mit der Ausnahme, dass sie den Speicherplatz in ihrem eigenen Prozess mit allen anderen Threads in diesem Prozess teilen. Ein Prozess "besitzt" alle ihm zugewiesenen Ressourcen, wie Speicher, Datei-Handles, Sockets, Geräte-Handles 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 Zeitspanne ausführt (auf einem einzelnen Prozessor). Der Scheduler weist jedem Thread Zeit (timeslicing) zu, und wenn der Thread innerhalb dieser Zeit nicht beendet ist, sperrt der Scheduler ihn vor (unterbricht ihn und wechselt zu einem anderen Thread). Auf einem Multiprozessor-Rechner können mehrere Threads parallel ausgeführt werden, da jeder Thread auf einem separaten Prozessor geplant werden kann (aber nicht muss).

Auf einer Einzelprozessor-Maschine werden Threads schnell in Zeitabschnitte unterteilt und zwischen ihnen umgeschaltet (unter Linux beträgt die Standardzeitscheibe 100 ms), wodurch sie gleichzeitig ablaufen. Sie können jedoch nicht (gleichzeitig) parallel ausgeführt werden, da ein Single-Core-Prozessor jeweils nur eine Aufgabe ausführen kann.

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

In einigen Kontexten verweisen Coroutinen möglicherweise auf stapelbare Funktionen, während Generatoren möglicherweise verweisen zu stapellosen Funktionen.

Fasern , leichte Fäden und Grüne Fäden sind andere Bezeichnungen 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. (Abhängig von der Sprache oder Implementierung können spezifischere technische Besonderheiten oder Unterschiede zwischen diesen Konzepten auftreten.)

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

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

Mac OS lässt nur die Zuweisung von ungefähr 2000 Threads zu, und Linux weist 8 MB Stack pro Thread zu und lässt nur so viele Threads zu, wie in den physischen Arbeitsspeicher passen.

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

143
llambda

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

Zuallererst, wenn Coroutinen gleichzeitig (nie in parallel) laufen, warum sollte jemand sie den Threads vorziehen?

Die Antwort ist, dass Coroutinen ein sehr hohes Maß an Parallelität mit sehr geringem Overhead bereitstellen können. Im Allgemeinen haben Sie in einer Thread-Umgebung höchstens 30-50 Threads, bevor die Menge an vergeudetem Overhead, die tatsächlich durch das Planen dieser Threads (durch den System-Scheduler) verursacht wird, die Zeit, die die Threads benötigen, erheblich verkürzt tatsächlich nützliche Arbeit leisten.

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 Arbeiten ausführt und von der Sie wissen, dass sie diese für einige Zeit blockiert (z. B. eine Netzwerkanforderung), können Sie mit einer Co-Routine sofort zu einer anderen Routine wechseln, ohne den System-Scheduler einbeziehen zu müssen diese entscheidung - ja du der programmierer muss spezifizieren wann co-routinen wechseln können.

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

Da Ihre Routinen jetzt an festgelegten Punkten zwischeneinander wechseln, können Sie jetzt auch gemeinsam genutzte Datenstrukturen nicht sperren (weil Sie Ihrem Code niemals anweisen würden, in der Mitte eines kritischen Abschnitts zu einer anderen Coroutine zu wechseln) )

Ein weiterer Vorteil ist die deutlich geringere Speichernutzung. Beim Thread-Modell muss jedem Thread ein eigener Stapel zugewiesen werden, sodass die Speichernutzung linear mit der Anzahl der Threads wächst. Bei Co-Routinen hat die Anzahl der Routinen keine direkte Beziehung zu Ihrer Speichernutzung.

Und schließlich erhalten Co-Routinen viel Aufmerksamkeit, weil in einigen Programmiersprachen (wie Python) Ihre Threads sowieso nicht parallel laufen können - sie laufen gleichzeitig wie Coroutinen, aber ohne den geringen Arbeitsspeicher und den freien Planungsaufwand.

86
Martin Konecny

Mit einem Wort: Befreiung. Coroutinen verhalten sich wie Gaukler, die sich immer wieder die eingeübten Punkte aushändigen. Threads (echte Threads) können an fast jeder Stelle unterbrochen und später fortgesetzt werden. Natürlich bringt dies alle möglichen Probleme mit Ressourcenkonflikten mit sich, daher Pythons berüchtigtes GIL - Global Interpreter Lock.

Viele Thread-Implementierungen ähneln eher Coroutinen.

18
Peter Rowell

Das hängt von der Sprache ab, die Sie verwenden. Zum Beispiel in Lua sie sind dasselbe (der Variablentyp einer Coroutine heißt thread).

Normalerweise, obwohl Coroutinen freiwilliges Nachgeben implementieren, wo (Sie) der Programmierer entscheidet, wo er yield, dh einer anderen Routine die Kontrolle übergibt.

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

9
Thomas Bonini