Während Threads die Ausführung von Code beschleunigen können, werden sie tatsächlich benötigt? Kann jeder Code mit einem einzelnen Thread erstellt werden oder gibt es etwas, das nur mit mehreren Threads erreicht werden kann?
programming-languages
multithreading
Wütender Vogel
quelle
quelle
Antworten:
Erstens können Threads die Ausführung von Code nicht beschleunigen. Sie sorgen nicht dafür, dass der Computer schneller läuft. Sie können lediglich die Effizienz des Computers steigern, indem sie Zeit verwenden, die sonst verschwendet würde. Bei bestimmten Verarbeitungsarten kann diese Optimierung die Effizienz erhöhen und die Laufzeit verkürzen.
Die einfache Antwort lautet ja. Sie können jeden Code schreiben, der auf einem einzelnen Thread ausgeführt werden soll. Beweis: Ein Einzelprozessorsystem kann Befehle möglicherweise nur linear ausführen. Wenn das Betriebssystem mehrere Ausführungszeilen hat, werden die Interrupts verarbeitet, der Status des aktuellen Threads wird gespeichert und ein weiterer Thread wird gestartet.
Die komplexe Antwort ist ... komplexer! Der Grund, warum Multithread-Programme häufig effizienter sind als lineare, liegt an einem Hardware- "Problem". Die CPU kann Berechnungen schneller ausführen als Speicher- und Festplatten-E / A. So wird beispielsweise ein "add" -Befehl viel schneller ausgeführt als ein "fetch". Caches und spezielles Abrufen von Programmbefehlen (der genaue Begriff ist hier nicht bekannt) können dies in gewissem Maße verhindern, aber das Geschwindigkeitsproblem bleibt bestehen.
Threading ist eine Möglichkeit, diese Nichtübereinstimmung zu beheben, indem die CPU für CPU-gebundene Anweisungen verwendet wird, während die E / A-Anweisungen ausgeführt werden. Ein typischer Thread-Ausführungsplan wäre wahrscheinlich: Daten abrufen, Daten verarbeiten, Daten schreiben. Angenommen, das Abrufen und Schreiben dauert 3 Zyklen, und die Verarbeitung dauert zur Veranschaulichung einen. Sie können sehen, dass der Computer während des Lese- oder Schreibvorgangs zwei Zyklen lang nichts tut ? Klar ist es faul und wir müssen unsere Optimierungspeitsche knacken!
Wir können den Prozess mit Threading umschreiben, um diese verschwendete Zeit zu nutzen:
Und so weiter. Natürlich ist dies ein etwas ausgeklügeltes Beispiel, aber Sie können sehen, wie diese Technik die Zeit nutzen kann, die sonst für das Warten auf E / A aufgewendet würde.
Beachten Sie, dass das oben gezeigte Threading die Effizienz nur bei stark an E / A gebundenen Prozessen steigern kann. Wenn ein Programm hauptsächlich Dinge berechnet, wird es nicht viele "Löcher" geben, in denen wir mehr arbeiten könnten. Außerdem gibt es einen Overhead von mehreren Anweisungen beim Wechseln zwischen Threads. Wenn Sie zu viele Threads ausführen, verbringt die CPU die meiste Zeit mit dem Umschalten und arbeitet nicht viel an dem Problem. Das nennt man Thrashing .
Das ist alles in Ordnung und gut für einen Single-Core-Prozessor, aber die meisten modernen Prozessoren haben zwei oder mehr Kerne. Threads dienen nach wie vor demselben Zweck - um die CPU-Auslastung zu maximieren. Diesmal können jedoch zwei separate Anweisungen gleichzeitig ausgeführt werden. Dies kann die Laufzeit um den Faktor verringern, der zur Verfügung steht, je nachdem, wie viele Kerne vorhanden sind, da der Computer tatsächlich Multitasking und nicht Kontextwechsel ausführt.
Bei mehreren Kernen bieten Threads eine Methode zum Aufteilen der Arbeit zwischen den beiden Kernen. Das oben Gesagte gilt jedoch weiterhin für jeden einzelnen Kern. Ein Programm, das eine maximale Effizienz mit zwei Threads auf einem Kern ausführt, wird höchstwahrscheinlich mit einer Spitzeneffizienz von etwa vier Threads auf zwei Kernen ausgeführt. (Die Effizienz wird hier durch minimale NOP-Befehlsausführungen gemessen.)
Die Probleme beim Ausführen von Threads auf mehreren Kernen (im Gegensatz zu einem einzelnen Kern) werden im Allgemeinen von der Hardware behoben. Die CPU stellt sicher, dass sie die entsprechenden Speicherplätze sperrt, bevor sie darauf liest / schreibt. (Ich habe gelesen, dass dafür ein spezielles Flag-Bit im Speicher verwendet wird, aber dies kann auf verschiedene Weise erreicht werden.) Als Programmierer mit höheren Sprachen müssen Sie sich keine Gedanken mehr über zwei Kerne machen müsste mit einer.
TL; DR: Threads können die Arbeit aufteilen, damit der Computer mehrere Aufgaben asynchron verarbeiten kann. Auf diese Weise kann der Computer mit maximaler Effizienz ausgeführt werden, indem die gesamte verfügbare Verarbeitungszeit genutzt wird, anstatt gesperrt zu werden, wenn ein Prozess auf eine Ressource wartet.
quelle
Nichts.
Einfache Beweisskizze:
Beachten Sie jedoch, dass sich darin eine große Annahme verbirgt: Die im einzelnen Thread verwendete Sprache ist Turing-vollständig.
So würde die interessantere Frage: „Kann das Hinzufügen nur Multi-Threading auf eine Nicht-Turing-complete Sprache macht es Turing-complete?“ Und ich glaube, die Antwort lautet "Ja".
Nehmen wir Total Functional Languages. [Für diejenigen, die nicht vertraut sind: So wie funktionale Programmierung mit Funktionen programmiert, programmiert die gesamte funktionale Programmierung mit Gesamtfunktionen.]
Total Functional Languages sind offensichtlich nicht Turing-vollständig: Sie können in einer TFPL keine Endlosschleife schreiben (das ist eigentlich so ziemlich die Definition von "total"), aber Sie können in einer Turing-Maschine, dh, es gibt mindestens ein Programm, das existiert Kann nicht in eine TFPL, sondern in eine UTM geschrieben werden, daher sind TFPLs weniger rechenintensiv als UTMs.
Sobald Sie jedoch ein Threading zu einer TFPL hinzufügen, erhalten Sie Endlosschleifen: Führen Sie einfach jede Iteration der Schleife in einem neuen Thread aus. Jeder einzelne Thread gibt immer ein Ergebnis zurück, daher ist es Total, aber jeder Thread erzeugt auch einen neuen Thread, der die nächste Iteration ad infinitum ausführt .
Ich denke, dass diese Sprache Turing-vollständig sein würde.
Zumindest beantwortet es die ursprüngliche Frage:
Wenn Sie eine Sprache, die nicht Endlosschleifen tun können, dann Multi-Threading können Sie Endlosschleifen tun.
Beachten Sie natürlich, dass das Laichen eines Threads ein Nebeneffekt ist und daher unsere erweiterte Sprache nicht nur nicht mehr Total ist, sondern nicht einmal mehr Functional.
quelle
Theoretisch kann alles, was ein Multithread-Programm tut, auch mit einem Single-Thread-Programm ausgeführt werden, nur langsamer.
In der Praxis kann der Geschwindigkeitsunterschied so groß sein, dass für die Aufgabe kein Single-Thread-Programm verwendet werden kann. Wenn beispielsweise jede Nacht ein Stapelverarbeitungsjob ausgeführt wird und das Fertigstellen eines einzelnen Threads mehr als 24 Stunden dauert, haben Sie keine andere Wahl, als ihn multithreadingfähig zu machen. (In der Praxis liegt der Schwellenwert wahrscheinlich sogar noch darunter: Oft müssen solche Aktualisierungsaufgaben am frühen Morgen abgeschlossen sein, bevor Benutzer das System wieder verwenden können. Außerdem können andere Aufgaben davon abhängen, die ebenfalls in derselben Nacht abgeschlossen sein müssen Die verfügbare Laufzeit kann nur wenige Stunden / Minuten betragen.)
Das Ausführen von Computerarbeiten an mehreren Threads ist eine Form der verteilten Verarbeitung. Sie verteilen die Arbeit über mehrere Threads. Ein weiteres Beispiel für eine verteilte Verarbeitung (mit mehreren Computern anstelle von mehreren Threads) ist der SETI-Bildschirmschoner: Es würde schrecklich viel Zeit in Anspruch nehmen, so viele Messdaten auf einem einzelnen Prozessor zu verarbeiten, und die Forscher würden es vorziehen, die Ergebnisse vor dem Ruhestand zu sehen Sie haben nicht das Budget, um einen Supercomputer für so lange Zeit zu mieten. Deshalb verteilen sie den Auftrag auf Millionen von Haushalts-PCs, um ihn billig zu machen.
quelle
Zwar bietet die Verwendung von Threads einige Leistungsvorteile, da Sie die Arbeit auf mehrere Kerne verteilen können, sie sind jedoch häufig mit einem hohen Preis verbunden.
Einer der Nachteile der Verwendung von Threads, die hier noch nicht erwähnt wurden, ist der Verlust der Ressourcenkompartimentierung, den Sie mit einzelnen Thread-Prozessbereichen erhalten. Angenommen, Sie stoßen auf einen Segfault. In einigen Fällen ist es möglich, dies in einer Multi-Prozess-Anwendung zu beheben, indem Sie das fehlerhafte Kind einfach sterben lassen und ein neues Kind neu erschaffen. Dies ist im Prefork-Backend von Apache der Fall. Wenn eine httpd-Instanz ausfällt, ist der schlimmste Fall, dass die bestimmte HTTP-Anforderung für diesen Prozess gelöscht wird, Apache jedoch ein neues untergeordnetes Element erzeugt und die Anforderung häufig nur erneut gesendet und bearbeitet wird. Das Endergebnis ist, dass Apache als Ganzes nicht mit dem fehlerhaften Thread beendet wird.
Eine weitere Überlegung in diesem Szenario sind Speicherverluste. Es gibt einige Fälle, in denen Sie einen Thread-Absturz problemlos handhaben können (unter UNIX ist die Wiederherstellung nach bestimmten Signalen - auch bei einem Fehler / einer Verletzung - möglich), aber selbst in diesem Fall haben Sie möglicherweise den gesamten von diesem Thread zugewiesenen Speicher verloren (Malloc, neu, etc.). Während Sie also möglicherweise weiterarbeiten, geht mit jedem Fehler / jeder Wiederherstellung im Laufe der Zeit immer mehr Speicher verloren. Auch hier gibt es einige Möglichkeiten, dies zu minimieren, beispielsweise die Verwendung von Speicherpools durch Apache. Dies schützt jedoch nicht vor Speicher, der möglicherweise von Drittanbieter-Bibliotheken zugewiesen wurde, die der Thread möglicherweise verwendet hat.
Und wie einige Leute bereits betont haben, ist es vielleicht am schwierigsten, Synchronisationsprimitive richtig zu verstehen. Dieses Problem selbst - nur die allgemeine Logik für all Ihren Code richtig zu machen - kann große Kopfschmerzen bereiten. Mysteriöse Deadlocks können in den seltsamsten Zeiten auftreten, und manchmal sogar erst, wenn Ihr Programm in der Produktion ausgeführt wird, was das Debuggen umso schwieriger macht. Hinzu kommt die Tatsache, dass Synchronisationsprimitive je nach Plattform (Windows vs. POSIX) häufig stark variieren und das Debuggen oft schwieriger ist, sowie die Möglichkeit, dass die Race-Bedingungen jederzeit gegeben sind (Start / Initialisierung, Laufzeit und Herunterfahren). Das Programmieren mit Threads hat für Anfänger wirklich wenig Gnade. Und auch für Experten, es gibt immer noch wenig Gnade, nur weil das Wissen über das Threading selbst die Komplexität im Allgemeinen nicht minimiert. Manchmal scheint jede Zeile von Thread-Code die Gesamtkomplexität des Programms exponentiell zu verschärfen und die Wahrscheinlichkeit zu erhöhen, dass ein versteckter Deadlock oder eine seltsame Race-Bedingung zu irgendeinem Zeitpunkt auftritt. Es kann auch sehr schwierig sein, Testfälle zu schreiben, um diese Dinge herauszufiltern.
Aus diesem Grund basieren einige Projekte wie Apache und PostgreSQL größtenteils auf Prozessen. PostgreSQL führt jeden Backend-Thread in einem separaten Prozess aus. Dies lindert natürlich immer noch nicht das Problem der Synchronisation und der Rennbedingungen, aber es bietet einiges an Schutz und vereinfacht in gewisser Weise die Dinge.
Mehrere Prozesse, die jeweils einen einzelnen Ausführungsthread ausführen, können viel besser sein als mehrere Threads, die in einem einzelnen Prozess ausgeführt werden. Mit dem Aufkommen eines Großteils des neuen Peer-to-Peer-Codes wie AMQP (RabbitMQ, Qpid usw.) und ZeroMQ ist es viel einfacher, Threads auf verschiedene Prozessbereiche und sogar auf Maschinen und Netzwerke aufzuteilen, was die Dinge erheblich vereinfacht. Aber es ist keine Wunderwaffe. Es bleibt immer noch Komplexität zu bewältigen. Sie verschieben nur einige Ihrer Variablen aus dem Prozessbereich in das Netzwerk.
Das Fazit ist, dass die Entscheidung, in die Domäne der Threads einzusteigen, keine leichte ist. Sobald Sie dieses Territorium betreten, wird fast augenblicklich alles komplexer und es treten ganz neue Arten von Problemen in Ihrem Leben auf. Es kann lustig und cool sein, aber es ist wie mit Atomkraft - wenn etwas schief geht, kann es schlecht und schnell gehen. Ich erinnere mich, dass ich vor vielen Jahren einen Kurs in Kritikalitätstraining besucht habe und sie zeigten Bilder von Wissenschaftlern in Los Alamos, die im Zweiten Weltkrieg in den Labors mit Plutonium spielten. Viele haben wenig oder gar keine Vorsichtsmaßnahmen gegen den Fall einer Exposition getroffen, und im Handumdrehen - mit einem einzigen hellen, schmerzlosen Blitz wäre alles für sie vorbei. Tage später waren sie tot. Richard Feynman bezeichnete dies später als " Kitzeln des Drachenschwanzes""So kann es sein, mit Fäden zu spielen (zumindest für mich). Zuerst scheint es ziemlich harmlos zu sein, und als du gebissen wirst, kratzst du dir am Kopf, wie schnell die Dinge sauer werden. Aber zumindest haben die Fäden gewonnen töte dich nicht
quelle
Erstens wird eine Single-Threaded-Anwendung niemals die Vorteile einer Multi-Core-CPU oder eines Hyper-Threading nutzen. Aber selbst auf einem einzelnen Kern hat eine Single-Threaded-CPU, die Multi-Threading ausführt, Vorteile.
Überlegen Sie sich die Alternative und ob Sie das glücklich macht. Angenommen, Sie haben mehrere Aufgaben, die gleichzeitig ausgeführt werden müssen. Zum Beispiel müssen Sie mit zwei verschiedenen Systemen kommunizieren. Wie geht das ohne Multithreading? Sie würden wahrscheinlich Ihren eigenen Scheduler erstellen und die verschiedenen Aufgaben aufrufen lassen, die ausgeführt werden müssen. Dies bedeutet, dass Sie Ihre Aufgaben in Teile aufteilen müssen. Wahrscheinlich müssen Sie einige Echtzeitbeschränkungen erfüllen, um sicherzustellen, dass Ihre Teile nicht zu viel Zeit in Anspruch nehmen. Andernfalls läuft der Timer bei anderen Aufgaben ab. Dies erschwert die Aufteilung einer Aufgabe. Je mehr Aufgaben Sie selbst verwalten müssen, desto mehr Aufteilung ist erforderlich und desto komplexer wird Ihr Scheduler, um alle Einschränkungen zu erfüllen.
Wenn Sie mehrere Threads haben, kann das Leben einfacher werden. Ein präventiver Scheduler kann einen Thread jederzeit stoppen, seinen Status beibehalten und einen anderen Thread neu starten. Es wird neu gestartet, wenn Ihr Thread an der Reihe ist. Vorteile: Die Komplexität, einen Scheduler zu schreiben, wurde bereits für Sie erledigt und Sie müssen Ihre Aufgaben nicht aufteilen. Der Scheduler ist auch in der Lage, Prozesse / Threads zu verwalten, die Sie selbst nicht kennen. Und wenn ein Thread nichts tun muss (auf ein Ereignis wartet), nimmt er auch keine CPU-Zyklen in Anspruch. Dies ist nicht so einfach, wenn Sie Ihren Down-Single-Threaded-Scheduler erstellen. (Etwas einzuschlafen ist nicht so schwierig, aber wie wacht es auf?)
Der Nachteil der Multithread-Entwicklung besteht darin, dass Sie sich mit Parallelitätsproblemen, Sperrstrategien usw. auskennen müssen. Das Entwickeln von fehlerfreiem Multithread-Code kann sehr schwierig sein. Und das Debuggen kann noch schwieriger sein.
quelle
Ja. Sie können keinen Code auf mehreren CPUs oder CPU-Kernen mit einem einzigen Thread ausführen.
Ohne mehrere CPUs / Kerne können Threads weiterhin parallel laufenden Code vereinfachen, z. B. die Client-Verarbeitung auf einem Server. Sie können jedoch dasselbe auch ohne Threads tun.
quelle
Bei Threads geht es nicht nur um Geschwindigkeit, sondern auch um Parallelität.
Wenn Sie keine Batch-Anwendung wie @Peter vorgeschlagen haben, sondern stattdessen ein GUI-Toolkit wie WPF, wie Sie mit nur einem Thread mit Benutzern und Geschäftslogik interagieren könnten?
Angenommen, Sie erstellen einen Webserver. Wie würden Sie mehr als einen Benutzer gleichzeitig mit nur einem Thread bedienen (vorausgesetzt, keine anderen Prozesse)?
Es gibt viele Szenarien, in denen ein einfacher Thread nicht ausreicht. Aus diesem Grund finden jüngste Fortschritte wie der Intel MIC-Prozessor mit mehr als 50 Kernen und Hunderten von Threads statt.
Ja, parallele und gleichzeitige Programmierung ist schwierig. Aber notwendig.
quelle
Durch Multi-Threading kann die GUI-Oberfläche bei langen Verarbeitungsvorgängen immer noch reagieren. Ohne Multithreading kann der Benutzer ein gesperrtes Formular nicht mehr beobachten, während ein langer Prozess ausgeführt wird.
quelle
Multithread-Code kann die Programmlogik blockieren und auf veraltete Daten zugreifen, wie dies mit einzelnen Threads nicht möglich ist.
Threads können einen obskuren Fehler von etwas übernehmen, von dem ein durchschnittlicher Programmierer erwartet, dass es ihn debuggt, und ihn in den Bereich verschieben, in dem Geschichten über das Glück erzählt werden, das erforderlich ist, um den gleichen Fehler mit heruntergefahrenen Hosen zu fangen, als ein alarmierender Programmierer zufällig nur den Fehler ansah richtiger Moment.
quelle
Apps, die sich mit dem Blockieren von E / A befassen und auch auf andere Eingaben (die grafische Benutzeroberfläche oder andere Verbindungen) reagieren müssen, können nicht als Singlethread ausgeführt werden
Die Hinzufügung von Überprüfungsmethoden in der IO-Bibliothek, um festzustellen, wie viel gelesen werden kann, ohne zu blockieren, kann dazu beitragen, dass jedoch nicht viele Bibliotheken vollständige Garantien dafür abgeben
quelle
Viele gute Antworten, aber ich bin mir nicht sicher, ob es so aussieht wie ich - Vielleicht bietet dies eine andere Sichtweise:
Threads sind nur eine Vereinfachung der Programmierung wie Objekte oder Akteure oder für Schleifen (Ja, alles, was Sie mit Schleifen implementieren, können Sie mit if / goto implementieren).
Ohne Threads implementieren Sie einfach eine State Engine. Ich musste das oft tun (das erste Mal, als ich es tat, hatte ich noch nie davon gehört - ich habe nur eine große switch-Anweisung gemacht, die von einer "State" -Variable gesteuert wird). Zustandsautomaten sind immer noch weit verbreitet, können aber ärgerlich sein. Mit Fäden verschwindet ein riesiger Brocken der Kesselplatte.
Sie machen es einer Sprache auch einfacher, ihre Laufzeitausführung in Multi-CPU-freundliche Blöcke zu unterteilen (so wie Actors, glaube ich).
Java bietet "grüne" Threads auf Systemen, auf denen das Betriebssystem KEINE Thread-Unterstützung bietet. In diesem Fall ist es einfacher zu erkennen, dass es sich eindeutig nur um eine Programmierabstraktion handelt.
quelle
Betriebssysteme verwenden ein Time-Slicing-Konzept, bei dem jeder Thread seine Ausführungszeit erhält und dann vorbelegt wird. Ein solcher Ansatz kann das derzeitige Threading ersetzen, aber das Schreiben von eigenen Schedulern in jeder Anwendung wäre übertrieben. Außerdem müssten Sie mit E / A-Geräten usw. arbeiten. Und würde eine gewisse Unterstützung von der Hardwareseite erfordern, damit Sie Interrupts auslösen können, damit Ihr Scheduler ausgeführt wird. Grundsätzlich würden Sie jedes Mal ein neues Betriebssystem schreiben.
Im Allgemeinen kann Threading die Leistung in Fällen verbessern, in denen Threads auf E / A warten oder inaktiv sind. Außerdem können Sie reaktionsschnelle Schnittstellen erstellen und Prozesse stoppen, während Sie lange Aufgaben ausführen. Threading verbessert außerdem die Leistung echter Multicore-CPUs.
quelle
Erstens können Threads zwei oder mehr Dinge gleichzeitig erledigen (wenn Sie mehr als einen Kern haben). Sie können dies zwar auch mit mehreren Prozessen durchführen, einige Aufgaben verteilen sich jedoch nicht sehr gut auf mehrere Prozesse.
Außerdem enthalten einige Aufgaben Leerzeichen, die Sie nicht so einfach umgehen können. Zum Beispiel ist es schwierig, Daten aus einer Datei auf der Festplatte zu lesen und gleichzeitig von Ihrem Prozess etwas anderes ausführen zu lassen. Wenn für Ihre Aufgabe unbedingt viele Daten von der Festplatte gelesen werden müssen, verbringt Ihr Prozess viel Zeit damit, auf die Festplatte zu warten, unabhängig davon, was Sie tun.
Zweitens können Threads es Ihnen ermöglichen, zu vermeiden, große Mengen Ihres Codes optimieren zu müssen, die nicht leistungskritisch sind. Wenn Sie nur einen Thread haben, ist jeder Code leistungskritisch. Wenn es blockiert, sind Sie gesunken - keine Aufgaben, die von diesem Prozess erledigt würden, können Fortschritte machen. Bei Threads wirkt sich ein Block nur darauf aus, dass der Thread und andere Threads mitkommen und Aufgaben bearbeiten können, die von diesem Prozess ausgeführt werden müssen.
Ein gutes Beispiel ist selten ausgeführter Fehlerbehandlungscode. Angenommen, bei einer Aufgabe ist ein sehr seltener Fehler aufgetreten, und der Code zur Behandlung dieses Fehlers muss in den Speicher geschrieben werden. Wenn der Datenträger ausgelastet ist und der Prozess nur einen einzelnen Thread enthält, kann kein Vorwärtsfortschritt ausgeführt werden, bis der Code zur Behandlung dieses Fehlers in den Speicher geladen werden kann. Dies kann zu einer stoßweisen Reaktion führen.
Ein anderes Beispiel ist, wenn Sie sehr selten eine Datenbanksuche durchführen müssen. Wenn Sie auf die Antwort der Datenbank warten, wird Ihr Code eine große Verzögerung erfahren. Sie möchten sich jedoch nicht die Mühe machen, den gesamten Code asynchron zu machen, da dies so selten vorkommt, dass Sie diese Suchvorgänge durchführen müssen. Mit einem Thread für diese Arbeit erhalten Sie das Beste aus beiden Welten. Ein Thread für diese Arbeit macht es nicht leistungskritisch, wie es sein sollte.
quelle