2013-01-05 5 views
6

Ich habe gerade gelesen den Artikel ‚Futures Done Right‘, und die Hauptsache, dass C++ 11 Versprechungen fehlen scheint Composite-Futures aus vorhandenenZukunft composability und boost :: wait_for_all

zu sein, dass die Schaffung ich bin auf der Suche jetzt bei der Dokumentation von boost::wait_for_any

aber folgendes Beispiel:

int calculate_the_answer_to_life_the_universe_and_everything() 
{ 
    return 42; 
} 

int calculate_the_answer_to_death_and_anything_in_between() 
{ 
    return 121; 
} 

boost::packaged_task<int> pt(calculate_the_answer_to_life_the_universe_and_everything); 
boost:: future<int> fi=pt.get_future(); 
boost::packaged_task<int> pt2(calculate_the_answer_to_death_and_anything_in_between); 
boost:: future<int> fi2=pt2.get_future(); 

.... 


int calculate_the_oscillation_of_barzoom(boost::future<int>& a, boost::future<int>& b) 
{ 
    boost::wait_for_all(a,b); 
    return a.get() + b.get(); 
} 

boost::packaged_task<int> pt_composite(boost::bind(calculate_the_oscillation_of_barzoom, fi , fi2)); 
boost:: future<int> fi_composite=pt_composite.get_future(); 

Was ist falsch mit diesem Ansatz zu composability? Ist dies ein gültiger Weg, um die Kompatibilität zu erreichen? Brauchen wir etwas elegantes syntaktisches edulcorant über diesem Muster?

Antwort

5

when_any und when_all sind absolut gültige Möglichkeiten, um Futures zu komponieren. Sie entsprechen beide der parallelen Komposition, wobei die zusammengesetzte Operation entweder auf eine oder auf alle zusammengesetzten Operationen wartet.

Wir brauchen auch sequentielle Zusammensetzung (die nicht in Boost.Thread ist). Dies könnte beispielsweise eine future<T>::then-Funktion sein, mit der Sie eine Operation in die Warteschlange stellen können, die den Wert der Zukunft verwendet und ausgeführt wird, wenn die Zukunft bereit ist. Es ist möglich, dies selbst zu implementieren, aber mit einem Effizienzkompromiss. Herb Sutter spricht darüber in seinem recent Channel9 video.

N3428 ist ein Entwurfsvorschlag zum Hinzufügen dieser Funktionen (und mehr) zur C++ - Standardbibliothek. Sie sind alle Bibliotheksfunktionen und fügen der Sprache keine neue Syntax hinzu. Darüber hinaus ist N3328 ein Vorschlag zum Hinzufügen von Syntax für fortsetzbare Funktionen (wie async/await in C# verwenden), die intern future<T>::then verwenden.

+0

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

+0

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

+0

@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. –

3

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.