En travaillant avec le modèle threadé de C++ 11, j'ai remarqué que
std::packaged_task<int(int,int)> 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';
semblent faire exactement la même chose. Je comprends qu'il pourrait y avoir une différence majeure si je courais std::async
avec std::launch::deferred
, mais y en at-il un dans ce cas?
Quelle est la différence entre ces deux approches et, plus important encore, dans quels cas d’utilisation devrais-je utiliser l’une plutôt que 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 commencera pas par lui-même, vous devez l'invoquer:
std::packaged_task<int()> 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
D'autre part, std::async
Avec launch::async
Tentera d'exécuter la tâche dans un autre thread:
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 l'avenir renvoyé a un état partagé particulier, qui exige que future::~future
Bloque:
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 vraiment asynchrone, vous devez conserver le future
renvoyé, 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, voir l'article de Herb Sutter async
et ~future
, qui décrit le problème, et Scott Meyer std::futures
De std::async
Ne sont pas spéciaux , qui décrit les idées. Notez également que ce comportement a été spécifié dans C++ 14 et supérieur , mais également implémenté couramment 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<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);
std::cout << f.get() << "\n";
En outre, un packaged_task
Doit être appelé avant que vous appeliez f.get()
, sinon votre programme sera gelé car le futur ne sera jamais prêt:
std::packaged_task<int(int,int)> 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 effectuées et ne vous en souciez pas quand elles sont terminées, et std::packaged_task
Si vous souhaitez les résumer pour les déplacer vers d'autres threads ou les appeler. plus tard. Ou, pour citer Christian :
En fin de compte, un
std::packaged_task
Est simplement une fonctionnalité de niveau inférieur permettant d'implémenterstd::async
(C'est pourquoi il peut en faire plus questd::async
S'il est utilisé avec d'autres éléments de niveau inférieur, commestd::thread
). En termes simples, unstd::packaged_task
Est unstd::function
Lié à unstd::future
Et unstd::async
Encapsule et appelle unstd::packaged_task
(Éventuellement dans un autre thread) .
p> La tâche empaquetée contient une paire tâche [function or function object]
et future/promesse. Lorsque la tâche exécute une instruction return, elle provoque set_value(..)
sur la promesse de packaged_task
.
a> Étant donné l'avenir, la promesse et la tâche de package, nous pouvons créer des tâches simples sans trop vous soucier des threads [thread est simplement quelque chose nous donnons pour exécuter une tâche].
Cependant, nous devons considérer le nombre de threads à utiliser ou s'il est préférable d'exécuter une tâche sur le thread actuel ou sur un autre, etc. Ces décisions peuvent être gérées par un programme de lancement de thread appelé async()
, qui décide de créer un nouveau fichier. un fil ou recycler un ancien ou simplement exécuter la tâche sur le fil actuel. Ça retourne un avenir.
"Le modèle de classe std :: packaged_task enveloppe toute cible appelable (fonction, expression lambda, expression de liaison ou autre objet fonction) afin de pouvoir être appelée de manière asynchrone. Sa valeur de retour ou exception levée est stockée dans un état partagé accessible. via std :: objets futurs. "
"La fonction 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."