En travaillant avec le modèle fileté de C ++ 11, j’ai remarqué que
std::packaged_task task([](int a, int b) { return a + b; }); auto f = task.get_future(); task(2,3); std::cout << f.get() << '\n';
et
auto f = std::async(std::launch::async, [](int a, int b) { return a + b; }, 2, 3); std::cout << f.get() << '\n';
semble faire exactement la même chose. Je comprends qu’il pourrait y avoir une différence majeure si j’ai couru std::async
avec std::launch::deferred
, mais y en a-t-il une dans ce cas?
Quelle est la différence entre ces deux approches et, plus important encore, dans quel cas d’utilisation dois-je utiliser l’une ou l’autre?
En fait, l’exemple que vous venez de donner montre les différences si vous utilisez une fonction assez longue, telle que
//! sleeps for one second and returns 1 auto sleep = [](){ std::this_thread::sleep_for(std::chrono::seconds(1)); return 1; };
Un packaged_task
ne démarre pas seul, vous devez l’invoquer:
std::packaged_task task(sleep); auto f = task.get_future(); task(); // invoke the function // You have to wait until task returns. Since task calls sleep // you will have to wait at least 1 second. std::cout << "You can see this after 1 second\n"; // However, f.get() will be available, since task has already finished. std::cout << f.get() << std::endl;
std::async
En revanche, std::async
avec launch::async
essaiera d'exécuter la tâche dans un thread différent:
auto f = std::async(std::launch::async, sleep); std::cout << "You can see this immediately!\n"; // However, the value of the future will be available after sleep has finished // so f.get() can block up to 1 second. std::cout << f.get() << "This will be shown after a second!\n";
Mais avant d’essayer d’utiliser async
pour tout, gardez à l’esprit que le futur renvoyé a un état partagé spécial, qui exige que future::~future
blocs future::~future
:
std::async(do_work1); // ~future blocks std::async(do_work2); // ~future blocks /* output: (assuming that do_work* log their progress) do_work1() started; do_work1() stopped; do_work2() started; do_work2() stopped; */
Donc, si vous voulez un réel asynchrone, vous devez conserver le future
retourné, ou si vous ne vous souciez pas du résultat si les circonstances changent:
{ auto pizza = std::async(get_pizza); /* ... */ if(need_to_go) return; // ~future will block else eat(pizza.get()); }
Pour plus d'informations à ce sujet, consultez l'article async
et ~future
Herb Sutter, qui décrit le problème, et les std::futures
de std::async
Scott Meyer ne sont pas spéciaux , ce qui décrit les idées. Notez également que ce comportement a été spécifié dans C ++ 14 et les versions ultérieures , mais également généralement implémenté dans C ++ 11.
En utilisant std::async
vous ne pouvez plus exécuter votre tâche sur un thread spécifique, où std::packaged_task
peut être déplacé vers d'autres threads.
std::packaged_task task(...); auto f = task.get_future(); std::thread myThread(std::move(task),2,3); std::cout << f.get() << "\n";
De plus, une f.get()
packaged_task
doit être appelée avant d’appeler f.get()
, sinon votre programme se fige car le futur ne sera jamais prêt:
std::packaged_task task(...); auto f = task.get_future(); std::cout << f.get() << "\n"; // oops! task(2,3);
Utilisez std::async
si vous voulez que certaines choses soient faites et ne vous souciez pas vraiment de leur finalisation, et std::packaged_task
si vous voulez boucler les choses pour les déplacer vers d'autres threads ou les appeler plus tard. Ou, pour citer Christian :
À la fin, un
std::packaged_task
est juste une fonctionnalité de bas niveau pour implémenterstd::async
(c'est pourquoi il peut faire plus questd::async
s'il est utilisé avec d'autres choses de bas niveau, commestd::thread
). Simplement dit, unstd::packaged_task
est unstd::function
lié à unstd::future
etstd::async
et appelle unstd::packaged_task
(éventuellement dans un thread différent).
“Le modèle de classe std :: packaged_task enveloppe toute cible appelable (fonction, expression lambda, expression de liaison ou autre object fonction) pour pouvoir être appelée de manière asynchrone. Sa valeur de retour ou son exception est stockée dans un état partagé accessible à travers std :: future objects. ”
“La fonction de modèle asynchrone exécute la fonction f de manière asynchrone (potentiellement dans un thread séparé) et renvoie un std :: future qui contiendra éventuellement le résultat de cet appel de fonction.”
p> La tâche empaquetée contient une tâche [function or function object]
et une paire avenir / promesse. Lorsque la tâche exécute une instruction return, la valeur set_value(..)
sur la promesse de packaged_task
.
a> Étant donné Future, promesse et tâche, nous pouvons créer des tâches simples sans trop nous soucier des threads [le thread est juste quelque chose que nous donnons pour exécuter une tâche].
Cependant, nous devons tenir compte du nombre de threads à utiliser ou de la meilleure exécution d’une tâche sur le thread en cours ou sur un autre, etc. Une telle séquence peut être gérée par un lanceur de thread appelé async()
, qui décide de créer un nouveau thread ou recycler un ancien ou simplement exécuter la tâche sur le thread en cours. Il retourne un avenir.