kennen:parallelverarbeitung
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
— | kennen:parallelverarbeitung [2020-01-03 16:30] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ====== Parallelverarbeitung ====== | ||
+ | |||
+ | Hinweis: | ||
+ | [[kennen: | ||
+ | Im folgenden geht es um // | ||
+ | |||
+ | |||
+ | ===== Leichtgewichtige Prozesse ===== | ||
+ | ==== Threads starten ==== | ||
+ | Mit '' | ||
+ | Deren genaue Reihenfolge ist unbestimmt. Sie können sich auch zeitlich überlappen. | ||
+ | <code cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | void ausgabe(std:: | ||
+ | { | ||
+ | std:: | ||
+ | out << std:: | ||
+ | std::cout << out.str(); // get message out in one piece | ||
+ | } | ||
+ | |||
+ | void nebeneinander() | ||
+ | { | ||
+ | std::thread t1(ausgabe, " | ||
+ | std::thread t2(ausgabe, "Kann ich nicht.\n" | ||
+ | ausgabe(" | ||
+ | // ... | ||
+ | </ | ||
+ | Die parallel abzuarbeitenden Funktionen können auch Funktoren oder Lambda-Ausdrücke sein. | ||
+ | Dem Konstruktor können nach dem Funktionsnamen Wertparameter für deren Aufruf mitgegeben werden. | ||
+ | Referenzen auf gemeinsam genutzte Ressourcen werden mit '' | ||
+ | ==== Threads beenden ==== | ||
+ | Ein Thread endet, sobald die übernommene Funktion verlassen wurde. | ||
+ | Der aufrufende Thread (// | ||
+ | bis der von ihm gestartete parallele Ablauffaden abgearbeitet ist | ||
+ | und ihn dann zusammenführen oder ihn vorher abkoppeln. | ||
+ | |||
+ | <code cpp> | ||
+ | // ... | ||
+ | t1.join(); | ||
+ | t2.detach(); | ||
+ | std:: | ||
+ | } | ||
+ | </ | ||
+ | '' | ||
+ | oder mit '' | ||
+ | Mit der Methode '' | ||
+ | damit andere Threads aktiv werden können. | ||
+ | |||
+ | Threads besitzen Verschiebesemantik: | ||
+ | Mit der Methode '' | ||
+ | |||
+ | Mitunter ist es sinnvoll, die Anzahl der Prozessoren, | ||
+ | '' | ||
+ | Wenn dies scheitert, liefert die Funktion '' | ||
+ | |||
+ | |||
+ | |||
+ | ===== Gemeinsame Ressourcen ===== | ||
+ | ==== Wettrennen ==== | ||
+ | // | ||
+ | veränderbare Ressourcen führen zu undefiniertem Programmverhalten. | ||
+ | Kritische Bereiche werden durch gegenseitigen Ausschluss (//mutual exclusion// | ||
+ | '' | ||
+ | Am Ende des kritischen Bereichs wird die Mutex-Variable wieder freigegeben. | ||
+ | Nun kann der nächste Thread diesem Block betreten. | ||
+ | |||
+ | Der '' | ||
+ | Die Typen '' | ||
+ | verfügen über Methoden | ||
+ | '' | ||
+ | die wie auch '' | ||
+ | Mutex-Methoden kann man selbst aufrufen --- es ist aber einfacher, Mutexe über Sperren zu steuern. | ||
+ | ==== Sperren ==== | ||
+ | Sperren '' | ||
+ | der Mutex-Variable in ihrem Konstruktor bzw. Destruktor. | ||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | void ausgabe(std:: | ||
+ | { | ||
+ | static std::mutex mutex; | ||
+ | auto myLock = std:: | ||
+ | std::cout << msg << ' | ||
+ | } | ||
+ | </ | ||
+ | Die Sperre '' | ||
+ | die Methoden '' | ||
+ | |||
+ | Bei '' | ||
+ | |||
+ | Der Sperrenzustand lässt sich mit '' | ||
+ | Der Besitz an einem Mutex wird durch '' | ||
+ | |||
+ | ^ ^Konstruktorparameter ^ ^ | ||
+ | |auch '' | ||
+ | | |'' | ||
+ | |nur '' | ||
+ | | |'' | ||
+ | | |'' | ||
+ | | |'' | ||
+ | | |'' | ||
+ | |||
+ | |||
+ | ==== Bedingungen ==== | ||
+ | Bedingungsvariablen '' | ||
+ | können belegte Sperren zeitweise wieder freigeben, | ||
+ | damit andere Threads weiterarbeiten können. | ||
+ | Ist die zur Fortführung des Threads notwendige Bedingung erfüllt, | ||
+ | kann dieser von einem anderen durch die Methoden '' | ||
+ | die Sperre zurück erlangen und seine Arbeit fortsetzen. | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | // double accounts[BANKSIZE]; | ||
+ | std::mutex mutex; | ||
+ | std:: | ||
+ | |||
+ | void transfer(int from, int to, double amount) | ||
+ | { | ||
+ | std:: | ||
+ | sufficientFunds.wait(myLock, | ||
+ | |||
+ | accounts[from] -= amount; | ||
+ | accounts[to] | ||
+ | | ||
+ | sufficientFunds.notifyAll(); | ||
+ | } | ||
+ | </ | ||
+ | Dabei ist '' | ||
+ | <code cpp> | ||
+ | while (!predicate()) variable.wait(lock); | ||
+ | </ | ||
+ | Für '' | ||
+ | ==== Verklemmung ==== | ||
+ | Eine Verklemmung (// | ||
+ | und durch zyklische Abhängigkeit keiner der beteiligten Threads fortfahren kann: | ||
+ | <code cpp> | ||
+ | Account a, b; | ||
+ | std::thread t1(give, std:: | ||
+ | std::thread t2(give, std:: | ||
+ | </ | ||
+ | Die Funktionen '' | ||
+ | erlangen alle Sperren der Liste unabhängig von der Reihenfolge ohne Verklemmungsrisiko. | ||
+ | Mit der Klasse '' | ||
+ | <code cpp> | ||
+ | void give(Account& | ||
+ | { | ||
+ | // std:: | ||
+ | // std:: | ||
+ | // std:: | ||
+ | std:: | ||
+ | from.take(money); | ||
+ | to.add(money); | ||
+ | } | ||
+ | </ | ||
+ | Damit sind nicht alle möglichen Ursachen von Verklemmungen beseitigt. | ||
+ | Sie können ebenso eintreten, wenn die erwartete Bedingung nie eintritt | ||
+ | oder der wartende Thread nicht benachrichtigt wird. | ||
+ | Eine " | ||
+ | |||
+ | ==== Threadsichere Initialisierung ==== | ||
+ | Globale Daten werden mit '' | ||
+ | Lokale '' | ||
+ | Betritt ein zweiter Thread die Funktion vor Abschluss der Initialisierung, | ||
+ | |||
+ | Als '' | ||
+ | Auch sie werden beim ersten Aufruf im Thread sicher initialisiert. | ||
+ | |||
+ | Mit '' | ||
+ | |||
+ | <code cpp> | ||
+ | Account a, b; | ||
+ | std:: | ||
+ | |||
+ | void erstausstattung(double amount) | ||
+ | { | ||
+ | a.add(amount); | ||
+ | b.add(amount); | ||
+ | } | ||
+ | |||
+ | void work() | ||
+ | { | ||
+ | std:: | ||
+ | | ||
+ | // ... jetzt kann Zahlungsverkehr beginnen | ||
+ | } | ||
+ | </ | ||
+ | Die optionalen Argumente werden wie bei '' | ||
+ | Die Aufgabe wird für das gleiche '' | ||
+ | Wirft die einmalig aufzurufende Funktion allerdings eine Ausnahme, | ||
+ | gilt die Aufgabe als nicht erledigt, die Ausnahme wird an den Aufrufer weitergereicht. | ||
+ | Ein weiterer Aufruf von '' | ||
+ | Leider bleibt durch einen [[https:// | ||
+ | )) | ||
+ | ===== Zeitversetzte Auswertung ===== | ||
+ | ==== Künftige Ergebnisse ==== | ||
+ | Durch '' | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | int frage() { /* es dauert ... */ return 42; } | ||
+ | |||
+ | void liefern_auf_bestellung() | ||
+ | { | ||
+ | std:: | ||
+ | std::cout << "Die Antwort lautet: ..."; | ||
+ | std::cout << antwort.get() <<' | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Der Aufruf '' | ||
+ | Die Abfrage des Ergebnisses blockiert, falls die Berechnung noch andauert. | ||
+ | Vor dem Funktor kann als Startverhalten '' | ||
+ | Ohne diese Angabe ist das Startverhalten der Implementierung überlassen. | ||
+ | |||
+ | Die Methode '' | ||
+ | Der Wert kann einmalig gelesen werden, | ||
+ | danach liefert '' | ||
+ | '' | ||
+ | '' | ||
+ | '' | ||
+ | |||
+ | Ein '' | ||
+ | |||
+ | ==== Aufträge ==== | ||
+ | Funktoren werden in | ||
+ | '' | ||
+ | verpackt und durch einen Klammeroperator mit geeigneten Parametern gestartet. | ||
+ | Ein Auftrag kann zur Ausführung an einen anderen Thread übertragen werden. | ||
+ | Das Ergebnis oder eine geworfene Ausnahme werden über ein mit '' | ||
+ | |||
+ | <code cpp> | ||
+ | int frage1(int n) { /* schwer zu tun */ return 6*n; } | ||
+ | |||
+ | void schick_mir_eine_antwort() | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | std::thread t(std:: | ||
+ | t.detach(); | ||
+ | std::cout << antwort.get() <<' | ||
+ | } | ||
+ | </ | ||
+ | ==== Versprechen ==== | ||
+ | Versprechen '' | ||
+ | den flexibelsten Kommunikationskanal zwischen den nebenläufigen Prozessen. | ||
+ | Der Zugriff auf das künftige Resultat blockiert, solange kein Wert oder keine Ausnahme hinterlegt wurde. | ||
+ | Danach kann die aufgerufene Prozedur noch weiterlaufen. | ||
+ | <code cpp> | ||
+ | void ansage(std:: | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | std::cout << "Die Antwort wird euch nicht gefallen ... "; | ||
+ | p.set_value(42); | ||
+ | // ... weitere 6,5 Mio Jahre nachdenken ... | ||
+ | } | ||
+ | catch(...) | ||
+ | { | ||
+ | p.set_exception(std:: | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void zur_ablage() | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | std::thread t(& | ||
+ | std::cout << antwort.get() << ' | ||
+ | t.join(); | ||
+ | } | ||
+ | </ | ||
+ | ===== Sperrenfreie Kommunikation ===== | ||
+ | Sperren sind eine kostspielige Angelegenheit, | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | int countdown(std:: | ||
+ | { | ||
+ | int done = 0, nr; | ||
+ | while ( (nr = int(--jobs)) >= 0) ++done; | ||
+ | return done; | ||
+ | } | ||
+ | |||
+ | void gemeinsam_gehts_besser() | ||
+ | { | ||
+ | int n = 1000000; | ||
+ | std:: | ||
+ | auto mode = std:: | ||
+ | auto cnt1 = std:: | ||
+ | auto cnt2 = std:: | ||
+ | int a = cnt1.get(), b = cnt2.get(); | ||
+ | |||
+ | std::cout << a << " + " << b << " = " << a+b << ' | ||
+ | assert(a+b == n); | ||
+ | } | ||
+ | </ | ||