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.
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 asynchronestd::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"
.
La fonction anonyme asynchrone est créée et commence à s'exécuter.
La fonction anonyme asynchrone est détruite.
main
l'exécution est bloquée jusqu'à la fin de la fonction.
"async"
Est imprimé.
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)
.
La fonction anonyme asynchrone est créée et commence à s'exécuter.
Delay(1000)
qui retarde l'impression de "async"
Immédiatement.main
l'exécution continue.
"main"
Est imprimé.Fin de la portée de main
.
La fonction anonyme asynchrone est détruite.
main
l'exécution est bloquée jusqu'à la fin de la fonction.
"async"
Est imprimé.
Il imprime
async main
, cela signifie-t-il que le thread principal attend queasync
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 queasync
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.
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.
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.