Punkte für die Verwendung des Wortes edulcorant. :)
Das Problem mit Ihrem Beispielcode ist, dass Sie alles in Aufgaben verpacken, aber Sie nie Zeitplan diese Aufgaben zur Ausführung!
int calculate_the_answer_to_life() { ... }
int calculate_the_answer_to_death() { ... }
std::packaged_task<int()> pt(calculate_the_answer_to_life);
std::future<int> fi = pt.get_future();
std::packaged_task<int()> pt2(calculate_the_answer_to_death);
std::future<int> fi2 = pt2.get_future();
int calculate_barzoom(std::future<int>& a, std::future<int>& b)
{
boost::wait_for_all(a, b);
return a.get() + b.get();
}
std::packaged_task<int()> pt_composite([]{ return calculate_barzoom(fi, fi2); });
std::future<int> fi_composite = pt_composite.get_future();
Wenn ich an dieser Stelle schreiben
pt_composite();
int result = fi_composite.get();
mein Programm wird für immer blockieren. Es wird nie abgeschlossen, weil pt_composite
auf calculate_barzoom
blockiert wird, die auf wait_for_all
blockiert wird, die auf beiden fi
und fi2
blockiert ist, jeweils von denen keiner wird jemals vollständig, bis jemand pt
oder pt2
ausführt. Und niemand wird sie jemals ausführen, weil mein Programm blockiert ist!
Sie bedeutete mir wahrscheinlich so etwas zu schreiben:
std::async(pt);
std::async(pt2);
std::async(pt_composite);
int result = fi_composite.get();
Diese funktioniert. Aber es ist extrem ineffizient - wir erzeugen drei Worker-Threads (über drei Aufrufe an async
), um zwei Threads arbeiten zu können. Dieser dritte Thread - der laufende pt_composite
- wird sofort erzeugt werden, und dann sitzen Sie einfach schlafen bis pt
und pt2
sind fertig ausgeführt.Das ist besser als Spinnen, aber es ist deutlich schlechter als nicht vorhanden: es bedeutet, dass unsere Thread-Pool hat einen weniger Arbeiter als es hätte. In einer plausiblen Thread-Pool-Implementierung mit nur einem Thread pro CPU-Kern und vielen Aufgaben, die zu jeder Zeit kommen, heißt das, dass wir einen CPU-Kern im Leerlauf haben, weil der Worker-Thread bedeutete laufen auf diesem Kern ist derzeit innerhalb blockiert.
Was wir wollen tun erklären ist unsere Absichten deklarativ:
int calculate_the_answer_to_life() { ... }
int calculate_the_answer_to_death() { ... }
std::future<int> fi = std::async(calculate_the_answer_to_life);
std::future<int> fi2 = std::async(calculate_the_answer_to_death);
std::future<int> fi_composite = std::when_all(fi, fi2).then([](auto a, auto b) {
assert(a.is_ready() && b.is_ready());
return a.get() + b.get();
});
int result = fi_composite.get();
und dann die Bibliothek und den Scheduler zusammenarbeiten, um das Richtige zu tun: keine Worker-Thread erzeugen, das kann‘ t sofort mit seiner Aufgabe fortfahren. Wenn der Endbenutzer sogar eine einzelne Codezeile schreiben muss, die explizit schläft, wartet oder blockiert, geht die Leistung definitiv verloren.
Mit anderen Worten: Spawn kein Worker Thread vor seiner Zeit.
Natürlich ist es möglich all dies in Standard C++ zu tun, ohne Bibliotheksunterstützung; so ist die Bibliothek selbst implementiert! Aber es ist ein großer Schmerz, von Grund auf neu zu implementieren, mit vielen subtilen Tücken; Deshalb ist es gut, dass die Unterstützung der Bibliothek bald kommt.
Der Vorschlag ISO N3428 in Roshan Shariff's answer erwähnt wurde als N3857 aktualisiert und N3865 bietet noch mehr Komfortfunktionen.
wir haben bereits resumable Funktionen als Bibliothek in boost: siehe 'boost :: context'. die Implementierung einer Bibliothek 'when_all' wäre eine variadische Template-Variante des von mir geposteten Codebeispiels? oder gibt es noch mehr Magie, die ich verpasse? – lurscher
Natürlich müssen all diese packaged_tasks irgendwann an einen Thread-Pool oder 'asio :: io_service' für asynchrone Ausführung übergeben werden, aber die Semantik sollte nicht beeinflusst werden, wenn/wann die Berechnungen ausgeführt werden – lurscher
@lurscher Implementieren' when_any' nur zu den vorhandenen Bibliotheksmerkmalen ist möglich, aber nicht unbedingt effizient. Ich kann keinen Weg sehen, dies zu tun, ohne eine große Anzahl von Threads hervorzubringen, sie auf allen zu blockieren und darauf zu warten, dass einer von ihnen aufwacht. Auf der anderen Seite, wenn das Betriebssystem einige Unterstützung bietet, könnte eine viel effizientere Implementierung für einen Standardbibliotheksanbieter möglich sein. –