2015-08-04 14 views
6

Eng verwandte Fragen wurden vor gefragt:Warum müssen/müssen UI-Frameworks single-threaded sein?

Aber die Antworten auf diese Fragen lassen mich immer noch auf einige Punkte unklar.

  • Der Fragesteller hat von der ersten Frage gestellt, ob Multi-Threading-Performance helfen würde, und die Beantworter sagten meist, dass es nicht, weil es sehr unwahrscheinlich ist, dass der GUI auf modernes die Engpass in einer 2D-Anwendung wäre Hardware. Aber das scheint mir eine hinterhältige Debattier-Taktik zu sein. Sicher, wenn Sie Ihre Anwendung sorgfältig so strukturiert haben, dass sie nur UI-Aufrufe im UI-Thread ausführt, haben Sie keinen Engpass. Aber das könnte eine Menge Arbeit erfordern und Ihren Code komplizierter machen. Wenn Sie einen schnelleren Kern hätten oder Benutzeroberflächenaufrufe von mehreren Threads ausführen könnten, wäre es vielleicht nicht sinnvoll.

  • Ein allgemein befürwortetes architektonisches Design besteht darin, View-Komponenten zu haben, die keine Callbacks haben und nichts außer ihren Nachkommen absperren müssen. Können Sie unter einer solchen Architektur keinen Thread Methoden für View-Objekte aufrufen lassen, indem Sie pro Objekt Sperren verwenden, ohne Angst vor Deadlock?

  • Ich bin weniger zuversichtlich über die Situation mit UI-Steuerelemente, aber solange ihre Rückrufe nur vom System aufgerufen werden, warum sollten sie spezielle Deadlock-Probleme verursachen? Immerhin, wenn die Callbacks etwas Zeit erfordern, werden sie an einen anderen Thread delegieren, und dann sind wir gleich wieder im Fall mehrerer Threads.

  • Wie viel von dem Nutzen einer multi-threaded UI würden Sie erhalten, wenn Sie nur auf dem UI-Thread blockieren könnten? Weil verschiedene sich entwickelnde Abstraktionen über Async in der Tat Ihnen das ermöglichen.

  • Fast die gesamte Diskussion, die ich gesehen habe, geht davon aus, dass Nebenläufigkeit wird mit manuellen Sperren behandelt werden, aber es besteht ein breiter Konsens, dass manuelle Sperren ist eine schlechte Möglichkeit, Nebenläufigkeit in den meisten Kontexten zu verwalten. Wie ändert sich die Diskussion, wenn wir die Parallelität von Primitiven berücksichtigen, die die Experten uns empfehlen, mehr zu verwenden, wie z. B. Softwaretransaktionsspeicher oder Verzicht auf gemeinsam genutzten Speicher zugunsten der Nachrichtenübergabe (möglicherweise mit Synchronisation, wie in Go)?

Antwort

9

TL; DR

Es ist eine einfache Art und Weise der Sequenzierung zu zwingen, in einer Aktivität auftritt, die schließlich wird in der Folge sein sowieso (der Bildschirm zeichnet X-mal pro Sekunde, um) .

Diskussion

Handhabung lang gehegte Ressourcen, die eine einzige Identität innerhalb eines Systems haben in der Regel durch die sie vertreten mit einem einzigen Thread, Prozess, „Objekt“ oder was auch immer für eine Atomeinheit in Bezug getan wird Nebenläufigkeit in einer bestimmten Sprache. Zurück in den nicht-präventiven, nachlässig-kernigen, nicht geteilten, eintägigen TrueThread-Tagen wurde dies manuell durch Abrufen/Radfahren oder Schreiben eines eigenen Planungssystems durchgeführt. In einem solchen System hattest du immer noch ein 1: 1-Mapping zwischen Funktion/Objekt/Ding und singulären Ressourcen (oder du bist vor der achten Klasse verrückt geworden).

Dies ist derselbe Ansatz, der bei der Verarbeitung von Netzwerk-Sockets oder einer anderen langlebigen Ressource verwendet wird. Die GUI selbst ist nur eine von vielen solchen Ressourcen, die ein typisches Programm verwaltet, und typischerweise sind langlebige Ressourcen Orte, an denen die Reihenfolge der Ereignisse von Bedeutung ist.

Zum Beispiel würden Sie in einem Chatprogramm normalerweise keinen einzigen Thread schreiben. Sie hätten einen GUI-Thread, einen Netzwerk-Thread und vielleicht einen anderen Thread, der sich mit der Protokollierung von Ressourcen beschäftigt oder was auch immer. Es ist nicht ungewöhnlich, dass ein typisches System so schnell ist, dass es einfacher ist, die Protokollierung und Eingabe in den gleichen Thread zu legen, der GUI-Aktualisierungen durchführt, aber dies ist nicht immer der Fall. In allen Fällen wird jede Ressourcenkategorie jedoch am einfachsten durchdacht, indem ihnen ein einziger Thread zugewiesen wird, und das bedeutet ein Thread für das Netzwerk, ein Thread für die GUI und viele andere Threads sind für langlebige Operationen erforderlich oder Ressourcen verwaltet werden, ohne die anderen zu blockieren.

Um das Leben einfacher zu machen ist es gemeinsam nicht teilen Sie Daten direkt zwischen diesen Threads so viel wie möglich. Warteschlangen sind viel einfacher zu verstehen als Ressourcensperren und können Sequenzierung garantieren. Die meisten GUI-Bibliotheken stellen entweder Ereignisse in die Warteschlange (damit sie in der richtigen Reihenfolge ausgewertet werden können) oder übergeben Datenänderungen, die von Ereignissen benötigt werden, erhalten jedoch vor jedem Durchlauf der Repaint-Schleife eine Sperre für den Zustand der GUI. Es ist egal, was vorher passiert ist, das Einzige, was beim Malen des Bildschirms wichtig ist, ist der Zustand der Welt rechts dann. Dies unterscheidet sich ein wenig von dem typischen Netzwerkfall, bei dem alle Daten in der richtigen Reihenfolge gesendet werden müssen und einige davon nicht zu berücksichtigen sind.

So GUI-Frameworks sind nicht Multithreaded, per se, es ist die GUI-Schleife, die ein einziger Thread sein muss, um diese einzelne lang gehaltene Ressource zu verwalten. Programmierbeispiele, die typischerweise von Natur aus trivial sind, sind oft single-threaded, wobei die gesamte Programmlogik im selben Prozess/Thread wie die GUI-Schleife läuft, aber dies ist in komplexeren Programmen nicht typisch.

zu summieren

Da Scheduling hart ist, ist Verwaltung gemeinsam benutzte Daten noch schwieriger, und eine einzelne Ressource nur seriell ohnehin zugegriffen werden kann, einen einzelnen Thread jede lange gehaltene Ressource und jeden langen darzustellen verwendet Das Ausführen von Prozeduren ist eine typische Methode zum Strukturieren von Code. GUIs sind nur eine Ressource unter mehreren, die ein typisches Programm verwalten wird. So sind "GUI-Programme" keinesfalls single-threaded, sondern typischerweise GUI-Bibliotheken.

In trivialen Programmen gibt es keinen Nachteil beim Einfügen anderer Programmlogik in den GUI-Thread, aber dieser Ansatz fällt auseinander, wenn erhebliche Lasten auftreten oder das Ressourcenmanagement entweder viel Blockieren oder Abfragen erfordert, weshalb Sie oft siehe Ereigniswarteschlange, Signal-Slot-Nachrichten-Abstraktionen oder andere Ansätze für Multithreading/-verarbeitung, die in den staubigen Ecken fast jeder GUI-Bibliothek erwähnt werden (und hier schließe ich Spielbibliotheken ein - während libs normalerweise erwarten, dass du im Wesentlichen bauen willst) eigene Widgets um dein eigenes UI-Konzept, die Grundprinzipien sind sehr ähnlich, nur ein bisschen untergeordnet.

[Nebenbei habe ich in letzter Zeit viel Qt/C++ und Wx/Erlang gemacht. The Qt docs do a good job of explaining approaches to multi-threading, die Rolle der GUI-Schleife, und wo Qt Signal/Slot-Ansatz in die Abstraktion passt (so müssen Sie nicht viel über Nebenläufigkeit/Sperren/Sequenzierung/Planung/etc denken). Erlang ist inhärent gleichzeitig, aber wx itself is typically started as a single OS process that manages a GUI update loop und Erlang-Beiträge aktualisieren Ereignisse als Nachrichten, und GUI-Ereignisse werden als Nachrichten an die Erlang-Seite gesendet - wodurch normales Erlang-Codieren ermöglicht wird, aber ein einziger Punkt der GUI-Ereignissequenzierung bereitgestellt wird mach seine GUI-Update-Schleife Sache.]

+0

Danke, ich werde diese Links morgen auschecken.Nur um klar zu stellen, ich bin mir sicher, dass GUI-Programme oft stark Multithread sind. Das ist einer der Gründe, warum der einzelne UI-Thread für Programmierer restriktiv sein kann! Mein Verständnis war/ist, dass der Hauptgrund für die Einschränkung ist, Deadlocks (oder andere Nebenläufigkeitsfehler) zu vermeiden. Vielleicht sind nachrichtenbasierte asynchrone APIs der richtige Ansatz (aber warum sind sie nicht beliebter?). – gmr

+0

@gmr Nach meiner Erfahrung war die Kombination von Nachrichtenübergabe und * Nicht-Freigabe-Status * ein großer Gewinn, weil man damit die Planung von Zustandsproblemen isolieren kann. Ein Grund, warum Message-Passing nicht sehr beliebt ist, ist, denke ich, dass das Vokabular von OOP "Nachrichten" mit "Calls" zusammenfasst. Sie sind nicht dasselbe. Eine Nachricht zwischen konkurrierenden Prozessen/Objekten wird besser durch eine Warteschlange dargestellt, die in einem eigenen Thread ausgeführt wird. In jedem Fall laufen viele GUI-Anwendungen in einem einzigen Thread, einfach weil es keine/geringe Ausführungsstrafe gibt, und es ist viel einfacher in algolartigen Sprachen zu argumentieren. – zxq9

+0

Ich habe heute darüber nachgedacht. Das Problem mit der Nachrichtenübergabe (async) besteht darin, dass Sie nie wissen, was der tatsächliche Status ist, da es eine Kombination aus dem in Komponenten gespeicherten Zustand und dem Status des ausstehenden Zustands in den Nachrichtenpumpen ist. Wenn Sie den Status nicht teilen, duplizieren Sie ihn natürlich. Ich mag die konzeptionelle Einfachheit des Musters. – Robinson

Verwandte Themen