Tut async(launch::async) in C++11 machen, thread-pools veraltet für die Vermeidung von teuren thread erstellen?
Ist es Locker sich auf diese Frage beziehen: Sind std::thread gebündelt in C++11?. Obwohl die Frage unterscheidet sich, das Ziel ist das gleiche:
Frage 1: Macht es noch Sinn die zu verwenden Sie Ihre eigenen (oder 3rd-party-Bibliothek), thread-pools zu vermeiden, teure thread erstellen?
Den Abschluss, die andere Frage war, dass Sie sich nicht darauf verlassen kann std::thread
gepoolt werden (es könnte oder könnte es nicht). Allerdings std::async(launch::async)
zu haben scheint, eine viel höhere chance zu einem Pool zusammengefasst werden.
Er glaube nicht, dass es gezwungen ist, die Norm, aber IMHO würde ich erwarten, dass alle guten C++11-Implementierungen verwenden würden, thread-pooling, wenn thread-Erstellung ist langsam. Nur auf Plattformen, wo es günstig einen neuen thread erstellen, würde ich erwarten, dass Sie immer laichen einen neuen thread.
Frage 2: Das ist genau das, was ich denke, aber ich habe keine Fakten um das zu beweisen. Ich kann sehr gut falsch sein. Ist es eine Vermutung?
Schließlich, hier habe ich einige Beispiel-code, der erste zeigt, wie ich denke, dass thread-Erstellung können ausgedrückt werden, indem async(launch::async)
:
Beispiel 1:
thread t([]{ f(); });
//...
t.join();
wird
auto future = async(launch::async, []{ f(); });
//...
future.wait();
Beispiel 2: Feuer-und-vergessen-thread
thread([]{ f(); }).detach();
wird
//a bit clumsy...
auto dummy = async(launch::async, []{ f(); });
//... but I hope soon it can be simplified to
async(launch::async, []{ f(); });
Frage 3: Würden Sie es vorziehen, die async
Versionen der thread
Versionen?
Der rest ist nicht mehr Teil der Frage, aber nur zur Klarstellung:
Warum muss der Rückgabewert zugewiesen werden, um eine dummy-variable?
Leider, in der aktuellen C++11 standard-Kräfte, die Sie erfassen den Rückgabewert von std::async
werden, sonst ist der Destruktor ausgeführt wird, die blockiert wird, bis die Aktion beendet. Es wird von einigen als ein Fehler in der standard - (z.B., von Herb Sutter).
Diesem Beispiel aus cppreference.com zeigt es schön:
{
std::async(std::launch::async, []{ f(); });
std::async(std::launch::async, []{ g(); }); //does not run until f() completes
}
Weiteren Klärung:
Ich weiß, dass thread-pools können auch andere legitime verwendet, aber in dieser Frage bin ich nur daran interessiert, den Aspekt der Vermeidung von teuren thread-Erstellung Kosten.
Ich denke, es gibt immer noch Situationen, wo thread-pools sind sehr nützlich, vor allem, wenn Sie mehr Kontrolle über Ressourcen.
Zum Beispiel, ein server kann entscheiden, zu handhaben, dass nur eine bestimmte Anzahl von Anfragen gleichzeitig garantieren schnelle Reaktionszeiten und erhöhen die Vorhersagbarkeit der Speichernutzung. Thread-pools sollte in Ordnung sein, hier.
Thread-lokalen Variablen kann auch ein argument sein für die eigene thread-pools, aber ich bin mir nicht sicher, ob es relevant ist, in der Praxis:
- Erstellen einen neuen thread mit
std::thread
startet, ohne initialisiert thread-lokalen Variablen. Vielleicht ist dies nicht das, was Sie wollen. - In threads hervorgebracht von
async
es ist etwas unklar für mich, weil der thread hätte wiederverwendet. Von meinem Verständnis, thread-lokale Variablen nicht garantiert werden zurückgesetzt, aber ich kann mich irren. - Mit Ihrem eigenen (mit fester Größe) thread-pools, auf der anderen Seite, gibt Ihnen die volle Kontrolle, wenn Sie es wirklich brauchen.
- "Aber
std::async(launch::async)
zu haben scheint, eine viel höhere chance zu einem Pool zusammengefasst werden." Nein, ich glaube, seinestd::async(launch::async | launch::deferred)
werden können gebündelt. Mit nurlaunch::async
die Aufgabe soll gestartet werden, in einem neuen thread, unabhängig davon, was andere tasks ausgeführt werden. Mit der Politiklaunch::async | launch::deferred
dann die Umsetzung, erhält zu wählen, die Politik, aber vor allem wird es, zu verzögern, die Wahl, die Politik. Das heißt, es kann warten, bis ein thread in einem thread-pool zur Verfügung stehen und dann wählen Sie die async-Politik. - Soweit ich weiß, nur VC++ verwendet einen thread-pool mit
std::async()
. Ich bin immer noch neugierig zu sehen, wie Sie die Unterstützung von nicht-trivialen thread_local Destruktoren in einem thread-pool. - Das kann sehr einfach realisiert werden, in deren Umsetzung mit RegisterWaitForSingleObject mit dem "Objekt" als das thread-handle. Wenn der thread beendet werden, dessen handle wird signalisiert werden, und der Rückruf in der Warteschlange ausgeführt im thread-pool. Der Rückruf kann dann rufen Sie nicht-trivialen Destruktor für TLS. Ich weiß nicht, ob Sie das tun (noch).
- Ich trat durch die libstdc++ kommt mit gcc 4.7.2 und festgestellt, dass, wenn die launch-Politik ist nicht genau
launch::async
dann wird es behandelt, als wäre es nurlaunch::deferred
und nie führt es-asynchron - also in der Tat, diese version von libstdc++ "wählt" immer latente gezwungen, es sei denn sonst. - Mein Punkt über thread_local Destruktoren war, dass die Zerstörung auf den thread beenden ist nicht ganz richtig, bei der Verwendung von thread-pools. Wenn eine Aufgabe ausgeführt wird asynchron ausgeführt wird, 'als ob in einem neuen thread', gemäß der Spezifikation, was bedeutet, dass jede async-task bekommt seinen eigenen thread_local Objekte. Ein thread-pool-basierte implementation hat, Besondere Sorgfalt walten, um sicherzustellen, dass die Aufgaben, welche die gleiche backing-thread immer noch so Verhalten, als wenn Sie Ihre eigenen thread_local Objekte. Betrachten Sie dieses Programm: pastebin.com/9nWUT40h
- Mit "als-ob in einem neuen thread" in der spec-war ein großer Fehler meiner Meinung nach.
std::async
hätte eine schöne Sache für die Leistung - es hätte der standard kurz ausgeführt-der task-Ausführung-system, natürlich gesichert durch einen thread-pool. Gerade jetzt, es ist nur einstd::thread
mit etwas Mist geheftet, um die thread-Funktion in der Lage sein, um einen Wert zurückzugeben. Oh, und Sie hat redundante "latente" Funktionen, die überschneidungen der Arbeit derstd::function
komplett. - Upvote für "den aktuellen standard C++11 Kräfte, die Sie erfassen den Rückgabewert von std::async, da sonst der Destruktor ausgeführt wird, die blockiert wird, bis die Aktion beendet." Ist es möglich, dass 'Zukunft' Verwandte features wurden implementiert mit shared_ptr, so hat es eine bessere Speicherverwaltung und brauchen nicht zu streng / komisch auf Zerstörung Teil?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Frage 1:
Wechselte ich diese aus dem original, weil das original war falsch. Ich hatte den Eindruck, dass Linux-thread-Erstellung war sehr Billig und nach Tests habe ich festgestellt, dass der overhead des Funktionsaufrufs in einem neuen thread im Vergleich zu einem normalen ist enorm. Der Aufwand für die Erstellung von einem thread zu behandeln, eine Funktion aufrufen, ist so etwas wie 10000 oder mehr mal länger als bei einem einfachen Aufruf der Funktion. Also, wenn Sie die Ausstellung viele kleine Funktion ruft ein thread pool kann eine gute Idee sein.
Es ist ziemlich offensichtlich, dass der standard-C++ - Bibliothek, die Schiffe mit g++ nicht thread-pools. Aber ich kann definitiv sehen, ein Fall für Sie. Selbst mit dem overhead, dass schieben Sie den Aufruf durch irgendeine Art von inter-thread-Warteschlange, es würde wahrscheinlich billiger sein, als der Start eines neuen thread. Und der standard dies erlaubt.
IMHO, das Linux-kernel-Leute sollten daran arbeiten, dass thread-Erstellung billiger als es derzeit ist. Aber die standard C++ - Bibliothek sollten auch erwägen, den pool zu implementieren
launch::async | launch::deferred
.Und die OP ist richtig, mit
::std::thread
starten ein thread natürlich erzwingt die Erstellung eines neuen thread, anstatt eine aus einem pool. So::std::async(::std::launch::async, ...)
bevorzugt.Frage 2:
Ja, im Grunde ist diese 'implizit', startet einen thread. Aber wirklich, es ist immer noch ziemlich offensichtlich, was vor sich geht. Also ich denke nicht wirklich, dass das Wort implizit ist ein besonders gutes Wort.
Ich bin auch nicht davon überzeugt, dass Sie zu zwingen, zu warten, für die Rückkehr vor der Zerstörung ist unbedingt ein Fehler. Ich weiß nicht, dass Sie sollten mit den
async
Aufruf von create 'daemon' - threads, die nicht wieder erwartet. Und wenn Sie werden wieder erwartet, es ist nicht OK zu ignorieren Ausnahmen.Frage 3:
Persönlich, ich mag thread startet zu explizit sein. Ich lege viel Wert auf Inseln, wo Sie garantieren können, die einen seriellen Zugriff. Andernfalls Sie am Ende mit veränderbarer Zustand, dass Sie immer auf der Verpackung eines mutex irgendwo Rum und erinnern, es zu benutzen.
Ich mochte die Arbeit Warteschlange Modell eine ganze Menge besser als die "Zukunft" - Modell, weil es "Inseln der Serie" herumliegen, so können Sie effektiver behandeln änderbarer Zustand.
Aber wirklich, es hängt davon ab, was genau Sie tun.
Performance-Test
So, getestet habe ich die performance der verschiedenen Methoden aufrufen, die Dinge und kam mit diesen zahlen auf einem 8-core (AMD Ryzen 7 2700X) system läuft Fedora 29 clang kompiliert mit version 7.0.1 und libc++ (nicht libstdc++):
Und einheitlichen, auf meinem MacBook Pro 15" (Intel(R) Core(TM) i7-7820HQ CPU @ 2.90 GHz) mit
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
unter OSX 10.13.6, bekomme ich diese:Für den worker-thread, fing ich an, einen Faden, dann eine lockless-Warteschlange zu senden, die Anträge auf einen anderen thread warten und dann für ein "It' s done" zu Antworten zurück zu senden.
Dem "nichts Tun" ist nur zu testen, der Aufwand der Testumgebung.
Es ist klar, dass der Aufwand der Einführung eines thread ist enorm. Und auch den worker-thread mit der inter-thread-queue verlangsamt Dinge nach unten mit einem Faktor von 20 oder so auf Fedora-25 in einer VM, und von über 8 auf native OS X.
Erstellte ich ein Bitbucket-Projekt hält der code, den ich verwendet für den performance-test. Es kann hier gefunden werden: https://bitbucket.org/omnifarious/launch_thread_performance
futex
system-call und generell verbesserte Linux-threading deutlich. Auch: cs.utexas.edu/~witchel/372/Vorträge/POSIX_Linux_Threading.pdf::std::mutex
. In zählen bis 10000 mit zwei threads gab es so etwas wie 10 Anrufe, um denfutex
system nennen. Die Intel-Leute hören die Linux-Leute. Wenn ein CPU-Wechsel würde helfen, es kann passieren. Warum müssen Sie alles tun, mit der TLB?