web-dev-qa-db-fra.com

Quelle est la différence entre packaged_task et async

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?

112
nijansen

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;
};

Tâche emballée

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";

Inconvénient

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.

Autres différences

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);

TL; DR

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émenter std::async (C'est pourquoi il peut en faire plus que std::async S'il est utilisé avec d'autres éléments de niveau inférieur, comme std::thread). En termes simples, un std::packaged_task Est un std::function Lié à un std::future Et un std::async Encapsule et appelle un std::packaged_task (Éventuellement dans un autre thread) .

142
Zeta

Tâche empaquetée vs asynchrone

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.

2
user5096772

"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."

1
Radoslav.B