Viele Sprachen, die Multithreading unterstützen, bieten eine Aktion, die es einem Thread ermöglicht, einen Kontextwechsel zu anderen Threads anzubieten. Zum Beispiel Haskells yield
.
Die Dokumentation sagt jedoch nicht, was der tatsächliche Anwendungsfall ist. Wenn es angebracht ist, diese yield -Funktionen zu verwenden, und wenn nicht?
Vor kurzem habe ich einen solchen Anwendungsfall in Verbesserung der Leistung von Warp wieder gesehen Wenn ein Netzwerkserver eine Nachricht sendet, lohnt es sich, yield
aufzurufen, bevor erneut versucht wird, Daten zu empfangen, da der Client einige Zeit benötigt, um die Antwort zu verarbeiten und eine weitere Anfrage zu stellen.
Ich würde gerne andere Beispiele oder Richtlinien sehen, wenn der Aufruf von yield
etwas bringt.
Ich bin hauptsächlich an Haskell interessiert, aber es macht mir nichts aus, über andere Sprachen oder das Konzept im Allgemeinen zu lernen.
Hinweis: Das hat nichts mit Generatoren oder Coroutinen zu tun, wie zB yield
in Python oder Ruby.
GHCs IO-Manager verwendet yield
, um die Leistung zu verbessern. Die Verwendung kann auf github gefunden werden, aber ich werde einfügen es auch hier.
Ein hilfreicher Kommentar erklärt die Verwendung von yield
:
Wenn die [erste nicht blockierende] Abfrage keine Ereignisse findet, geben wir nach und setzen den Poll-Loop-Thread auf Ende der Haskell-Ausführungswarteschlange. Wenn es zurückkommt, machen wir noch eins non-blocking-Umfrage, falls wir Glück haben und bereit Ereignisse haben. Wenn dies auch keine Ereignisse zurückgibt, führen wir eine Blockierungsabfrage durch.
So yield
wird verwendet, um die Anzahl der blockierenden Abfragen zu minimieren, die EventManager
ausführen muss.
GHC setzt Threads nur an bestimmten sicheren Punkten aus (insbesondere bei der Zuweisung von Speicher). Zitieren Der Glasgow Haskell Compiler von Simon Marlow und Simon Peyton-Jones :
Ein Kontextwechsel tritt nur auf, wenn sich der Thread an einem sicheren Punkt befindet, an dem nur sehr wenig zusätzlicher Status gespeichert werden muss. Da wir einen genauen GC verwenden, kann der Stapel des Threads bei Bedarf verschoben und erweitert oder geschrumpft werden. Vergleichen Sie diese mit Betriebssystemthreads, bei denen jeder Kontextwechsel den gesamten Prozessorstatus speichern muss und Stapel unbeweglich sind, sodass ein großer Teil des Adressraums für jeden Thread vorab reserviert werden muss.
[...]
Allerdings hat die Implementierung ein Problem, dem Benutzer gelegentlich begegnen, insbesondere wenn sie Benchmarks ausführen. Wir erwähnten oben, dass Lightweight-Threads einen Teil ihrer Effizienz nur durch Kontextwechsel an "sicheren Punkten", Punkten im Code, den der Compiler als sicher bezeichnet, und dem internen Zustand der virtuellen Maschine (Stack, Heap, Register usw.) ableiten. ) ist in einem aufgeräumten Zustand und die Müllabfuhr könnte stattfinden. In GHC ist ein sicherer Punkt immer dann, wenn Speicher zugewiesen wird, was in fast allen Haskell-Programmen regelmäßig genug geschieht, so dass das Programm niemals mehr als ein paar Dutzend Befehle ausführt, ohne einen sicheren Punkt zu treffen. Es ist jedoch in hoch optimiertem Code möglich, Schleifen zu finden, die für viele Iterationen ausgeführt werden, ohne Speicher zuzuweisen. Dies tritt häufig in Benchmarks auf (z. B. Funktionen wie Fakultät und Fibonacci). Es tritt weniger häufig in echtem Code auf, obwohl es passiert. Das Fehlen von sicheren Punkten verhindert, dass der Scheduler ausgeführt wird, was nachteilige Auswirkungen haben kann. Es ist möglich, dieses Problem zu lösen, aber nicht ohne die Leistung dieser Schleifen zu beeinflussen, und oft ist es wichtig, dass jeder Zyklus in seinen inneren Schleifen gespeichert wird. Das kann nur ein Kompromiss sein, mit dem wir leben müssen.
Daher kann es vorkommen, dass ein Programm mit einer engen Schleife keine solchen Punkte hat und niemals zwischen Threads wechselt. Dann ist yield
notwendig, damit andere Threads ausgeführt werden können. Siehe diese Frage und diese Antwort .
Tags und Links haskell multithreading context-switch