web-dev-qa-db-fra.com

Confusion au sujet des threads lancés par std :: async avec le paramètre std :: launch :: async

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?

25
krispet krispet

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.

  1. 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.).
  2. 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.
  3. 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

37
bku_drytt

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

0
user3833817

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
0
kaidoe