std::async

Aufgaben mal eben schnell beiseite schieben und entkoppelt ausführen? Mit std::async stellt C++11 zumindest die ersten Bestandteile dafür zur Verfügung.

Video

Quellcode

#include <iostream>
#include <future>

int main()
{
  auto launch_policy = std::launch::async | std::launch::deferred;
  
  std::future<int> f1 = std::async(launch_policy, []() {
    std::cout << "1 gestartet\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "1 beendet\n";
    return 50;
  });
  auto f2 = std::async(launch_policy, []() {
    std::cout << "2 gestartet\n";
    std::this_thread::sleep_for(std::chrono::seconds(7));
    std::cout << "2 beendet\n";
    return 20;
  });
  auto f3 = std::async(launch_policy, []() {
    std::cout << "3 gestartet\n";
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "3 beendet\n";
    return 10;
  });
  
  std::this_thread::sleep_for(std::chrono::seconds(2));
  
  std::cout << "main aufgewacht\n";
  
  std::cout << "Rückgabe 1: " << f1.get() << std::endl;
  std::cout << "Rückgabe 2: " << f2.get() << std::endl;
  std::cout << "Rückgabe 3: " << f3.get() << std::endl;
}

Erklärung

C++11 bietet mit std::async eine einfache Möglichkeit, die Ausführung eines Tasks von der Aufrufstelle zu entkoppeln. Entweder führt man den später aus oder parallel. Gesteuert wird das mit der sogenannten Launch Policy (der erste Parameter von std::async): std::launch::deferred führt den Aufruf später aus, std::launch::async startet einen neuen Thread und ruft den Task parallel auf. Kombiniert man beide mittels std::launch::async | std::launch::deferred, dann soll die Implementierung so lange neue Threads starten, wie es sich lohnt (bspw. bis die CPU-Kerne alle ausgelastet sind). Leider ist die Implementierung der Standardbibliothek von GCC noch nicht so weit und arbeitet dann immer wie std::launch::deferred.

std::async liefert ein std::future<>-Objekt zurück. Dieses repräsentiert das zukünftige Ergebnis des Tasks. Mit der Methode get() kann man dieses Ergebnis abrufen. Steht das Ergebnis noch nicht zur Verfügung, dann blockiert die Methode. Speziell bei std::launch::deferred kann das passieren, denn diese Policy startet den Task erst in dem Moment des Aufrufs von get(). Das ist allerdings insofern kein Schaden, als dass man auf den Ablauf des Tasks ja auch dann warten müsste, wenn man ihn direkt normal aufrufen würde. Der deferred-Policy ist eher für Fälle interessant, wo man sich bei der Erzeugung eines Tasks noch nicht sicher ist, ob dessen Ergebnis wirklich mal gebraucht wird (in anderen Programmiersprachen wie Haskell kennt man das als lazy evaluation). Erst wenn das der Fall ist, ruft man ihn auch wirklich auf.