it-swarm.com.de

Verwirrung über Threads, die von std :: async mit dem Parameter std :: launch :: async gestartet wurden

Ich bin etwas verwirrt über die std::async-Funktion.

Die Spezifikation besagt: asynchrone Operation wird "wie in einem neuen Ausführungsthread" ausgeführt (C++ 11 §30.6.8/11).

Nun, was soll das heißen?

Nach meinem Verständnis der Code

std::future<double> fut = std::async(std::launch::async, pow2, num);

starten Sie die Funktion pow2 in einem neuen Thread und übergeben Sie die Variable num über einen Wert an den Thread. Platzieren Sie das Ergebnis dann in der Zukunft in fut (sofern die Funktion pow2 eine Signatur wie double pow2(double); hat). . Aber in der Spezifikation heißt es "als ob", was das Ganze für mich neblig macht.

Die Frage ist:

Wird in diesem Fall immer ein neuer Thread gestartet? Hoffentlich. Ich meine für mich ist der Parameter std::launch::async in einer Weise sinnvoll, die ich ausdrücklich erkläre, dass ich tatsächlich einen neuen Thread erstellen möchte. 

Und der Code

std::future<double> fut = std::async(std::launch::deferred, pow2, num);

sollte faule Auswertung ermöglichen, indem der Aufruf der pow2-Funktion an dem Punkt verzögert wird, an dem ich etwas wie var = fut.get(); schreibe. In diesem Fall sollte der Parameter std::launch::deferred bedeuten, dass ich ausdrücklich sage, ich möchte keinen neuen Thread. Ich möchte nur sicherstellen, dass die Funktion aufgerufen wird, wenn der Rückgabewert erforderlich ist. 

Sind meine Annahmen korrekt? Wenn nicht, bitte erläutern.

Ich weiß auch, dass die Funktion standardmäßig wie folgt aufgerufen wird:

std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num);

In diesem Fall wurde mir gesagt, dass es von der Implementierung abhängt, ob ein neuer Thread gestartet wird oder nicht. Wieder, was soll das heißen?

25
krispet krispet

Die Funktionsvorlage std::async (Teil des <future>-Headers) dient zum Starten einer (möglicherweise) asynchronen Task. Es gibt ein std::future-Objekt zurück, das schließlich den Rückgabewert von std::asyncs Parameterfunktion enthält.

Wenn der Wert benötigt wird, rufen wir get () in der std::future-Instanz auf. Dies blockiert den Thread, bis die Zukunft fertig ist, und gibt dann den Wert zurück. std::launch::async oder std::launch::deferred kann als erster Parameter für std::async angegeben werden, um anzugeben, wie die Task ausgeführt wird.

  1. std::launch::async gibt an, dass der Funktionsaufruf in einem eigenen (neuen) Thread ausgeführt werden muss. (Berücksichtigen Sie den Kommentar von Benutzer @ T.C.).
  2. std::launch::deferred zeigt an, dass der Funktionsaufruf zurückgestellt werden soll, bis wait() oder get() zukünftig aufgerufen wird. Das Eigentum an der Zukunft kann auf einen anderen Thread übertragen werden, bevor dies geschieht.
  3. std::launch::async | std::launch::deferred zeigt an, dass die Implementierung wählen kann. Dies ist die Standardoption (wenn Sie selbst keine angeben). Es kann sich entscheiden, synchron zu laufen.

Wird in diesem Fall immer ein neuer Thread gestartet?

Aus 1. können wir sagen, dass immer ein neuer Thread gestartet wird.

Sind meine Annahmen [in std :: launch :: deferred] korrekt?

Aus 2. können wir sagen, dass Ihre Annahmen richtig sind.

Was soll das heißen? [In Bezug auf einen neuen Thread, der gestartet wird oder nicht von der Implementierung abhängt]

Da aus 3.std::launch::async | std::launch::deferred die Standardoption ist, bedeutet dies, dass die Implementierung der Vorlagenfunktion std::async entscheidet, ob ein neuer Thread erstellt wird oder nicht. Dies liegt daran, dass einige Implementierungen möglicherweise eine Überplanung planen.

WARNUNG

Der folgende Abschnitt bezieht sich nicht auf Ihre Frage, aber ich denke, dass es wichtig ist, daran zu denken.

Der C++ - Standard besagt, dass, wenn ein std::future den letzten Verweis auf den gemeinsamen Status enthält, der einem Aufruf einer asynchronen Funktion entspricht, der Destruktor von std :: future blockiert, bis der Thread für die asynchron laufende Funktion beendet ist. Eine von std::future zurückgegebene Instanz von std::async wird daher in ihrem Destruktor blockiert.

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

Dieser irreführende Code lässt Sie denken, dass die std::async-Aufrufe asynchron sind, tatsächlich sind sie synchron. Die von std::future zurückgegebenen std::async-Instanzen sind temporär und werden blockiert, da ihr Destruktor richtig aufgerufen wird, wenn std::async zurückgegeben wird, da sie keiner Variablen zugewiesen sind.

Der erste Aufruf von std::async wird für 2 Sekunden blockiert, gefolgt von weiteren 2 Sekunden Blockierung vom zweiten Aufruf von std::async. Wir denken vielleicht, dass der letzte Aufruf von std::async nicht blockiert wird, da wir die zurückgegebene std::future-Instanz in einer Variablen speichern. Da dies jedoch eine lokale Variable ist, die am Ende des Bereichs zerstört wird, wird sie tatsächlich für weitere 2 Sekunden blockiert am Ende des Gültigkeitsbereichs der Funktion, wenn die lokale Variable f zerstört wird.

Wenn Sie also die Funktion operation() aufrufen, wird der Thread, für den er aufgerufen wird, ungefähr 6 Sekunden lang synchron blockiert. Solche Anforderungen sind möglicherweise in einer zukünftigen Version des C++ - Standards nicht vorhanden.

Informationsquellen, aus denen ich diese Notizen zusammengestellt habe:

C++ - Parallelität in Aktion: Praktisches Multithreading, Anthony Williams

Scott Meyers 'Blogeintrag: http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html

37
bku_drytt

Das stimmt eigentlich nicht . Fügen Sie thread_local gespeicherten Wert hinzu, und Sie werden sehen, dass std::async run f1 f2 f3-Tasks in verschiedenen Threads vorhanden sind, aber mit demselben std::thread::id

0
user3833817

Ich war auch verwirrt und führte einen schnellen Test unter Windows durch, der zeigt, dass die asynchrone Zukunft auf den Threads des Betriebssystemthreads ausgeführt wird. Eine einfache Anwendung kann dies demonstrieren und in Visual Studio ausbrechen, werden auch die ausführenden Threads mit dem Namen "TppWorkerThread" angezeigt.

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

Ergibt eine Ausgabe ähnlich wie:

main thread id 4164
future run on thread 4188
future run on thread 4188
future run on thread 4188
0
kaidoe