web-dev-qa-db-fra.com

Async utilise-t-il toujours un autre thread / noyau / processus en C ++?

Comme je sais, async exécute une fonction dans un autre thread/processus/core et ne bloque pas le thread principal, mais est-ce toujours le cas?

J'ai le code suivant:

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});
puts("main");

Il imprime async main, cela signifie-t-il que le thread principal attend que async se termine?

Si je change comme suit:

auto f = async(launch::async,[]() // add "auto f = "
{
    Sleep(1000);
    puts("async");
});
puts("main");

Il imprime main async. Cela donne l'impression que main n'attend pas que async se termine.

26
Person.Junkie

Comme je sais, async exécute une fonction dans un autre thread/processus/core et ne bloque pas le thread principal, mais cela se produit-il toujours?

std::async est garanti pour s'exécuter sur un thread séparé uniquement si std::launch::async est passé comme premier argument:

  • std::launch::async: Un nouveau thread est lancé pour exécuter la tâche de manière asynchrone
  • std::launch::deferred La tâche est exécutée sur le thread appelant la première fois que son résultat est demandé (évaluation paresseuse)

La politique par défaut lancement est std::launch::async | std::launch::deferred.


std::async Renvoie std::future . le destructeur de std::future ne bloquera que si l'avenir a été renvoyé de std::async:

ces actions ne seront pas bloquées pour que l'état partagé devienne prêt, sauf qu'elles peuvent bloquer si toutes les conditions suivantes sont vraies: l'état partagé a été créé par un appel à std :: async, l'état partagé n'est pas encore prêt, et cela était la dernière référence à l'état partagé


  • Dans votre premier extrait de code, vous créez une expression rvalue qui est immédiatement détruite - donc "async" sera imprimé avant "main".

    1. La fonction anonyme asynchrone est créée et commence à s'exécuter.

    2. La fonction anonyme asynchrone est détruite.

      • main l'exécution est bloquée jusqu'à la fin de la fonction.

      • "async" Est imprimé.

    3. main l'exécution reprend.

      • "main" Est imprimé.

  • Dans votre deuxième extrait de code, vous créez une expression lvalue dont la durée de vie est liée à la variable f. f sera détruit à la fin de l'étendue de la fonction main - donc "main" sera imprimé avant "async" en raison de la Delay(1000).

    1. La fonction anonyme asynchrone est créée et commence à s'exécuter.

      • Il y a une Delay(1000) qui retarde l'impression de "async" Immédiatement.
    2. main l'exécution continue.

      • "main" Est imprimé.
    3. Fin de la portée de main.

    4. La fonction anonyme asynchrone est détruite.

      • main l'exécution est bloquée jusqu'à la fin de la fonction.

      • "async" Est imprimé.

27
Vittorio Romeo

Il imprime async main, cela signifie-t-il que le thread principal attend que async se termine?

Oui, mais c'est parce que vous ne capturez pas le futur retourné à partir de async. async est spécial en ce que le future renvoyé par lui se bloque dans le destructeur jusqu'à ce que le thread se termine. Puisque vous ne capturez pas le future retourné

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});

doit se terminer avant que la progression ne soit effectuée dans le thread en cours car celui renvoyé future est détruit à la fin de l'expression.

Il imprime main async. Cela donne l'impression que main n'attend pas que async se termine.

C'est ce que vous voulez vraiment quand vous appelez async. Puisque vous avez capturé l'avenir, votre thread principal est autorisé à continuer pendant que la tâche asynchrone est terminée. Puisque vous avez un retard dans ce thread main va s'imprimer avant le thread.

6
NathanOliver

Si vous passez std::launch::async, Alors std::async Doit exécuter la tâche comme si elle était exécutée dans son propre thread.

Le seul concept de threading en C++ est std::thread.

std::async Renvoie un std::future Avec une propriété unique; s'il est détruit, il bloque à la fin de la tâche stockée dans std::async. Cela vous intercepte lorsque vous ne capturez pas la valeur de retour; le std::future renvoyé est un temporaire sans nom qui prend naissance et est détruit à la "fin de cette ligne".

Cette destruction attend la fin de la tâche async.

Dans le cas où vous le stockez, ce délai attend que la variable f soit détruite, ce qui se trouve à la fin de main, c'est-à-dire après l'impression.

Notez qu'au moins une implémentation majeure de C++ 11, MSVC 2015 et 2017, a au mieux un marginalement conforme std::async Qui utilise un pool de threads au lieu de nouveaux threads. Ce pool de threads signifie qu'un ensemble d'appels async de longue durée peut empêcher d'autres appels async de s'exécuter.

L'utilisation d'un pool de threads est légale (tant qu'elle recrée des sections locales de threads), mais elle devrait essayer d'éviter la famine et de créer de nouveaux threads si tous les threads existants sont occupés pendant "trop ​​longtemps".

Il est marginalement conforme car la norme indique seulement que les threads "devraient" progresser. Les threads qui ne progressent jamais pour des raisons aléatoires sont légaux sous C++; et dans le sens, vous pourriez dire que c'est ce que std::async émule dans ces cas, passant ainsi le test comme si.

3

En effet, le destructeur de std::future (Renvoyé de std::async) Attend que sa tâche soit terminée.

Dans le premier extrait de code, l'objet std::future Temporaire renvoyé de std::async Est détruit à la fin de l'instruction, car comme indiqué en https://en.cppreference.com/w/cpp/langue/durée de vie

Tous les objets temporaires sont détruits comme dernière étape de l'évaluation de l'expression complète qui contient (lexicalement) le point où ils ont été créés

Par conséquent, avant d'exécuter l'instruction suivante, le destructeur de l'objet std::future Bloque jusqu'à ce que la tâche se termine, ce qui signifie que puts("async") est exécutée avant puts("main").

Cependant, dans le deuxième extrait de code, la valeur de retour de std :: async est déplacée dans un objet local, qui est détruit lors de la sortie de la portée. Par conséquent, la ligne avec async est exécutée sans bloc et la puts("main") est exécutée avant puts("async") (qui est bloquée par l'appel de Sleep) comme prévu .

Ce comportement est expliqué dans https://en.cppreference.com/w/cpp/thread/async comme:

Si le std :: future obtenu à partir de std :: async n'est pas déplacé ou lié à une référence, le destructeur du std :: future se bloquera à la fin de l'expression complète jusqu'à ce que l'opération asynchrone se termine, créant essentiellement du code tel que les synchrones suivants:

std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes

Dans Point 38 du livre Effective Modern C++ , cela s'exprime comme suit:

Le destructeur du dernier futur se référant à un état partagé pour une tâche non différée lancée via des blocs std :: async jusqu'à la fin de la tâche. En substance, le destructeur d'un tel avenir effectue une jointure implicite sur le thread sur lequel la tâche s'exécutant de manière asynchrone s'exécute.

0
Ozgur Murat