Je suis un peu confus à propos de la fonction std::async
.
La spécification dit: opération asynchrone en cours d'exécution "comme dans un nouveau thread d'exécution" (C++ 11 §30.6.8/11).
Maintenant, qu'est-ce que cela est supposé vouloir dire?
Dans ma compréhension, le code
std::future<double> fut = std::async(std::launch::async, pow2, num);
devrait lancer la fonction pow2
sur un nouveau thread et passer la variable num
au thread par valeur, puis ultérieurement, lorsque la fonction est terminée, placez le résultat dans fut
(tant que la fonction pow2
a une signature comme double pow2(double);
) . Mais la spécification indique "comme si", ce qui rend le tout un peu brumeux pour moi.
La question est:
Un nouveau fil est-il toujours lancé dans ce cas? J'espere. Je veux dire pour moi, le paramètre std::launch::async
a un sens, dans la mesure où je déclare explicitement que je veux en effet créer un nouveau thread.
Et le code
std::future<double> fut = std::async(std::launch::deferred, pow2, num);
devrait permettre lazy evaluation possible, en retardant l’appel de la fonction pow2
au point où j’écris quelque chose comme var = fut.get();
. Dans ce cas, le paramètre std::launch::deferred
, devrait signifier que je déclare explicitement, je ne veux pas de nouveau thread, je veux juste m'assurer que la fonction est appelée quand sa valeur de retour est requise.
Mes hypothèses sont-elles correctes? Si non, s'il vous plaît expliquer.
De plus, je sais que par défaut, la fonction est appelée comme suit:
std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num);
Dans ce cas, on m'a dit que le lancement ou non d'un nouveau thread dépend de l'implémentation. Encore une fois, qu'est-ce que cela est censé vouloir dire?
Le modèle de fonction std::async
(partie de l'en-tête <future>
) est utilisé pour démarrer une tâche (éventuellement) asynchrone. Il retourne un objet std::future
, qui contiendra éventuellement la valeur de retour de la fonction paramètre de std::async
.
Lorsque la valeur est nécessaire, nous appelons get () sur l'instance std::future
; cela bloque le thread jusqu'à ce que le futur soit prêt, puis renvoie la valeur. std::launch::async
ou std::launch::deferred
peut être spécifié en tant que premier paramètre à std::async
afin de spécifier le mode d'exécution de la tâche.
std::launch::async
indique que l'appel de fonction doit être exécuté sur son propre (nouveau) thread. (Prendre en compte le commentaire de l'utilisateur @ T.C.).std::launch::deferred
indique que l'appel de fonction doit être différé jusqu'à ce que wait()
ou get()
soit appelé ultérieurement. La propriété du futur peut être transférée à un autre thread avant que cela ne se produise.std::launch::async | std::launch::deferred
indique que l'implémentation peut choisir. C'est l'option par défaut (lorsque vous n'en spécifiez pas vous-même). Il peut décider de fonctionner de manière synchrone.Un nouveau fil est-il toujours lancé dans ce cas?
A partir de 1., on peut dire qu'un nouveau fil est toujours lancé.
Mes hypothèses [sur std :: launch :: deferred] sont-elles correctes?
De 2., nous pouvons dire que vos hypothèses sont correctes.
Qu'est-ce que c'est censé vouloir dire? [par rapport à un nouveau thread en cours de lancement ou non en fonction de l'implémentation]
À partir de 3., étant donné que std::launch::async | std::launch::deferred
est l'option par défaut, cela signifie que l'implémentation de la fonction de modèle std::async
décidera si elle créera un nouveau thread ou non. En effet, certaines implémentations peuvent être en train de vérifier une planification excessive.
ATTENTION
La section suivante n’est pas liée à votre question, mais je pense qu’il est important de garder à l’esprit.
La norme C++ dit que si un std::future
contient la dernière référence à l'état partagé correspondant à un appel à une fonction asynchrone, le destructeur de std :: future doit bloquer jusqu'à la fin du thread de la fonction asynchrone. Une instance de std::future
renvoyée par std::async
sera donc bloquée dans son destructeur.
void operation()
{
auto func = [] { std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); };
std::async( std::launch::async, func );
std::async( std::launch::async, func );
std::future<void> f{ std::async( std::launch::async, func ) };
}
Ce code trompeur peut vous faire penser que les appels std::async
sont asynchrones, ils sont en réalité synchrones. Les instances std::future
renvoyées par std::async
sont temporaires et bloquent car leur destructeur est appelé à droite lorsque std::async
est renvoyé car elles ne sont pas affectées à une variable.
Le premier appel à std::async
sera bloqué pendant 2 secondes, suivi de 2 secondes supplémentaires de blocage du second appel à std::async
. Nous pouvons penser que le dernier appel à std::async
ne bloque pas, puisque nous stockons son instance retournée std::future
dans une variable, mais comme il s’agit d’une variable locale détruite à la fin de la portée, elle se bloque pendant 2 secondes à la fin de l'étendue de la fonction, lorsque la variable locale f est détruite.
En d'autres termes, l'appel de la fonction operation()
bloquera le fil sur lequel il est appelé de manière synchrone pendant environ 6 secondes. De telles exigences pourraient ne pas exister dans une future version du standard C++.
Sources d'information utilisées pour compiler ces notes:
C++ Concurrency in Action: Multithreading pratique, Anthony Williams
Article de blog de Scott Meyers: http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html
Ce n'est pas vraiment vrai . Ajoutez thread_local
valeur stockée et vous verrez que c'est en fait std::async run f1 f2 f3
tâches dans différents threads, mais avec le même std::thread::id
Cela m'a aussi troublé et j'ai exécuté un test rapide sur Windows, qui montre que l'avenir asynchrone sera exécuté sur les threads du pool de threads du système d'exploitation. Une application simple peut le démontrer. Une rupture dans Visual Studio montrera également les threads en cours d'exécution nommés "TppWorkerThread".
#include <future>
#include <thread>
#include <iostream>
using namespace std;
int main()
{
cout << "main thread id " << this_thread::get_id() << endl;
future<int> f1 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f1.get();
future<int> f2 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f2.get();
future<int> f3 = async(launch::async, [](){
cout << "future run on thread " << this_thread::get_id() << endl;
return 1;
});
f3.get();
cin.ignore();
return 0;
}
Donnera un résultat similaire à:
main thread id 4164
future run on thread 4188
future run on thread 4188
future run on thread 4188