Warum können Fasern nicht mehrere Prozessoren verwenden?

8

Es scheint, dass der Unterschied zwischen Fasern und Fäden darin besteht, dass Fasern kooperativ geplant werden, während Fäden präventiv geplant werden. Der Punkt des Schedulers scheint eine Möglichkeit zu sein, eine ansonsten serielle Prozessorressource parallel zu betreiben, indem die CPU "zeitlich geteilt" wird. Ich gehe jedoch davon aus, dass auf einem Dual-Core-Prozessor, auf dem jeder Core seinen eigenen Thread ausführt, die Ausführung eines Threads nicht angehalten werden muss, damit der andere fortfährt, da sie nicht einen einzelnen Prozessor "zeitlich teilen".

Wenn der Unterschied zwischen Threads und Fasern darin besteht, wie sie vom Scheduler unterbrochen werden und eine Unterbrechung nicht erforderlich ist, wenn auf physisch getrennten Kernen ausgeführt wird, warum können Fasern dann nicht mehrere Prozessorkerne nutzen, wenn Threads dies können?

Verwirrungsquellen:

..mainwikipedia

  1. http://en.wikipedia.org/wiki/Fiber_%28computer_science%29

    Ein Nachteil ist, dass Fasern keine Multiprozessor-Maschinen verwenden können, ohne auch präventive Fäden zu verwenden

  2. http://en.wikipedia.org/wiki/Computer_multitasking#Multithreading

    ... [Fasern] neigen dazu, einige oder alle Vorteile von Gewinden auf Maschinen mit mehreren Prozessoren zu verlieren.

James M. Lay
quelle

Antworten:

9

Wie Sie in Ihrer Frage hervorheben, besteht der Hauptunterschied darin, ob der Scheduler jemals einen Thread vorwegnimmt oder nicht. Die Art und Weise, wie ein Programmierer über das Teilen von Datenstrukturen oder das Synchronisieren zwischen "Threads" nachdenkt, ist in präventiven und kooperativen Systemen sehr unterschiedlich.

In einem kooperativen System (das unter vielen Namen bekannt ist: kooperatives Multitasking , nicht präventives Multitasking , Threads auf Benutzerebene , grüne Threads und Fasern sind derzeit fünf gebräuchliche) wird dem Programmierer garantiert, dass sein Code atomar ausgeführt wird , solange Sie tätigen keine Systemaufrufe oder Anrufe yield(). Dies macht es besonders einfach, mit Datenstrukturen umzugehen, die von mehreren Fasern gemeinsam genutzt werden. Sofern Sie keinen Systemaufruf als Teil eines kritischen Abschnitts ausführen müssen, müssen kritische Abschnitte nicht markiert werden ( z. B. mit Mutex lockund unlockAufrufen). Also im Code wie:

x = x + y
y = 2 * x

Der Programmierer muss sich keine Sorgen machen, dass eine andere Faser gleichzeitig mit den Variablen xund arbeiten könnte y. xund ywerden aus der Perspektive aller anderen Fasern atomar zusammen aktualisiert. In ähnlicher Weise könnten alle Fasern eine kompliziertere Struktur aufweisen, wie z. B. ein Baum, und ein Anruf wie tree.insert(key, value)müsste nicht durch einen Mutex oder einen kritischen Abschnitt geschützt werden.

Im Gegensatz dazu ist in einem präemptiven Multithreading-System wie bei wirklich parallelen / Multicore-Threads jede mögliche Verschachtelung von Anweisungen zwischen Threads möglich, es sei denn, es gibt explizite kritische Abschnitte. Ein Interrupt und eine Preemption können zwischen zwei beliebigen Anweisungen auftreten. Im obigen Beispiel:

 thread 0                thread 1
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 read y
                         < thread 1 could read or modify x or y at this point
 add x and y
                         < thread 1 could read or modify x or y at this point
 write the result back into x
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 multiply by 2
                         < thread 1 could read or modify x or y at this point
 write the result back into y
                         < thread 1 could read or modify x or y at this point

Um auf einem präventiven System oder auf einem System mit wirklich parallelen Threads korrekt zu sein, müssen Sie jeden kritischen Abschnitt mit einer Art Synchronisation umgeben, z. B. einem Mutex lockam Anfang und einem Mutex unlockam Ende.

Glasfasern sind daher asynchronen E / A- Bibliotheken ähnlicher als präventiven Threads oder wirklich parallelen Threads. Der Glasfaserplaner wird aufgerufen und kann Fasern während E / A-Operationen mit langer Latenz wechseln. Dies kann den Vorteil mehrerer gleichzeitiger E / A-Vorgänge bieten, ohne dass Synchronisierungsvorgänge in kritischen Abschnitten erforderlich sind. Daher kann die Verwendung von Fasern möglicherweise eine geringere Programmierkomplexität aufweisen als präemptive oder wirklich parallele Threads. Die fehlende Synchronisation um kritische Abschnitte würde jedoch zu katastrophalen Ergebnissen führen, wenn Sie versuchen würden, die Fasern wirklich gleichzeitig oder präemptiv auszuführen.

Wanderlogik
quelle
Ich denke, einige Erwähnungen sollten wahrscheinlich erwähnt werden: 1. Hybridsysteme, bei denen das Thread-System auf Benutzerebene die Verteilung von (vielen) Threads auf Benutzerebene auf (wenige) CPU-Kerne übernimmt, und 2. die Tatsache, dass beim Programmieren auf "Bare Metal" Es ist möglich, Multiprocessing ohne Vorkaufsrecht zu erhalten.
dfeuer
1
@dfeuer Ich glaube nicht, dass die Frage nach all den verschiedenen Möglichkeiten fragt, wie man Multiprocessing nutzen kann. Die Frage beim Lesen lautet: "Warum können Fasern (auch als nicht präventive Aufgaben bezeichnet) nicht wie präventive Fäden behandelt werden?" Wenn Sie von einer echten Parallelität ausgehen, müssen Sie korrekt synchronisieren, damit Sie keine "Fasern" mehr haben.
Wandering Logic
1
Schöne Antwort. Fasern können keine Sicherheit garantieren, da das Programm davon ausgehen würde, dass es exklusiven Zugriff auf gemeinsam genutzte Ressourcen hat, bis es einen Interrupt-Punkt angegeben hat, an dem Threads davon ausgehen, dass an jedem Punkt ein Zugriff / eine Mutation möglich ist. Offensichtlich die sicherere Annahme, wenn mehrere wirklich parallele Knoten mit denselben Daten interagieren.
James M. Lay
6

Die Antwort ist eigentlich, dass sie könnten, aber es besteht der Wunsch, dies nicht zu tun.

Fasern werden verwendet, weil Sie damit steuern können, wie die Planung erfolgt. Dementsprechend ist es viel einfacher, einige Algorithmen unter Verwendung von Fasern zu entwerfen, da der Programmierer zu sagen hat, in welcher Faser zu einem bestimmten Zeitpunkt ausgeführt wird. Wenn Sie jedoch möchten, dass zwei Fasern gleichzeitig auf zwei verschiedenen Kernen ausgeführt werden, müssen Sie sie manuell planen.

Threads geben die Kontrolle darüber, welcher Code auf dem Betriebssystem ausgeführt wird. Im Gegenzug erledigt das Betriebssystem viele hässliche Aufgaben für Sie. Einige Algorithmen werden schwieriger, da der Programmierer weniger Einfluss darauf hat, welcher Code zu einem bestimmten Zeitpunkt ausgeführt wird, sodass unerwartetere Fälle auftreten können. Tools wie Mutexe und Semaphoren werden einem Betriebssystem hinzugefügt, um dem Programmierer gerade genug Kontrolle zu geben, um Threads nützlich zu machen und einen Teil der Unsicherheit zu beseitigen, ohne den Programmierer zu stören.

Dies führt zu etwas, das noch wichtiger ist als kooperativ oder präventiv: Fasern werden vom Programmierer gesteuert, während Threads vom Betriebssystem gesteuert werden.

Sie möchten keine Faser auf einem anderen Prozessor erzeugen müssen. Die Befehle auf Baugruppenebene sind dafür äußerst kompliziert und häufig prozessorspezifisch. Sie möchten nicht 15 verschiedene Versionen Ihres Codes schreiben müssen, um diese Prozessoren zu handhaben, also wenden Sie sich an das Betriebssystem. Die Aufgabe des Betriebssystems ist es, diese Unterschiede zu abstrahieren. Das Ergebnis ist "Threads".

Fasern laufen über Fäden. Sie rennen nicht alleine. Wenn Sie also zwei Fasern auf unterschiedlichen Kernen ausführen möchten, können Sie einfach zwei Threads erzeugen und auf jedem von ihnen eine Faser ausführen. In vielen Implementierungen von Fasern können Sie dies leicht tun. Der Mehrkernträger kommt nicht von den Fasern, sondern von den Gewinden.

Es wird leicht zu zeigen, dass Sie, wenn Sie keinen eigenen prozessorspezifischen Code schreiben möchten, nichts tun können, indem Sie mehreren Kernen Fasern zuweisen, was Sie nicht tun könnten, indem Sie Threads erstellen und jedem Fasern zuweisen. Eine meiner Lieblingsregeln für das API-Design lautet: "Eine API wird nicht erstellt, wenn Sie alles hinzugefügt haben, sondern wenn Sie nichts mehr zum Herausnehmen finden." Angesichts der Tatsache, dass Multi-Core perfekt verarbeitet wird, indem Fasern auf Threads gehostet werden, gibt es keinen Grund, die Faser-API durch Hinzufügen von Multi-Core auf dieser Ebene zu komplizieren.

Cort Ammon
quelle