2015-06-12 18 views
20

Sono un po 'confuso riguardo la funzione std::async.Confusione sui thread lanciati da std :: async con std :: launch :: parametro async

La specifica dice: operazione asincrona eseguita "come se in un nuovo thread di esecuzione" (C++ 11 §30.6.8/11).

Ora, cosa significa?

Nella mia comprensione, il codice

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

dovrebbe lanciare la funzione pow2 su un nuovo thread e passare la variabile num al filo per valore, poi in futuro, quando la funzione è fatto, posto il risultato è fut (a condizione che la funzione pow2 abbia una firma come double pow2(double);). Ma la specifica afferma "come se", il che rende l'intera faccenda un po 'annebbiata per me.

La domanda è:

È un nuovo thread sempre lanciato in questo caso? Lo spero. Intendo per me, il parametro std::launch::async ha senso in un modo che sto affermando esplicitamente che in effetti voglio creare un nuovo thread.

E il codice

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

dovrebbe fare valutazione pigra possibile, ritardando la chiamata pow2 funzione al punto dove scrivo qualcosa come var = fut.get();. In questo caso il parametro std::launch::deferred, dovrebbe significare che sto affermando esplicitamente, non voglio un nuovo thread, voglio solo assicurarmi che la funzione venga chiamata quando c'è bisogno del suo valore di ritorno.

Le mie ipotesi sono corrette? In caso contrario, si prega di spiegare.

Inoltre, so che per default la funzione è chiamata come segue:

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

In questo caso, mi hanno detto che se un nuovo thread sarà lanciata o meno dipende dall'implementazione. Ancora, cosa dovrebbe significare?

+4

"come se" significa che può teoricamente riutilizzare un thread esistente (ad esempio, in un pool di thread) fino a quando il il comportamento è indistinguibile. In pratica, pochissime (se ce ne sono) implementazioni lo fanno perché "come se un nuovo thread" richiedesse di distruggere e ricreare tutte le variabili locali del thread. –

+0

@ T.C. o implementare (pesanti) variabili locali di coroutine. Ciascun thread riceve una coroutine predefinita e 'thread_local' è coroutine locale. Il 'async' può creare una coroutine, che la coroutine può essere ritagliata in un altro thread ed eseguire. Vale a dire, emulare il threading (con le coroutine) sopra un modello di thread fornito dal sistema operativo? – Yakk

+1

Ho sentito che 'std :: async' è rotto, è vero? –

risposta

23

Il modello di funzione std::async (parte del modello <future>) viene utilizzato per avviare un'attività (eventualmente) asincrona. Restituisce un oggetto std::future, che alla fine manterrà il valore di ritorno della funzione parametro std::async.

Quando il valore è necessario, si chiama get() nell'istanza std::future; questo blocca il thread fino a quando il futuro è pronto e quindi restituisce il valore. std::launch::async o std::launch::deferred possono essere specificati come primo parametro su std::async per specificare come viene eseguita l'attività.

  1. std::launch::async indica che la chiamata di funzione deve essere eseguita sul proprio (nuovo) thread. (Prendere in considerazione il commento dell'utente @ T.C.).
  2. std::launch::deferred indica che la chiamata di funzione deve essere posticipata fino a quando non viene chiamato su wait() o get(). La proprietà del futuro può essere trasferita su un'altra discussione prima che ciò accada.
  3. std::launch::async | std::launch::deferred indica che l'implementazione può scegliere. Questa è l'opzione predefinita (quando non ne specifichi uno tu stesso). Può decidere di eseguire in modo sincrono.

È una nuova discussione sempre avviata in questo caso?

Da 1., possiamo dire che viene sempre lanciato un nuovo thread.

Le mie ipotesi [su std :: launch :: deferred] sono corrette?

Da 2., possiamo dire che le vostre ipotesi sono corrette.

Che cosa dovrebbe significare? [In relazione a un nuovo thread in fase di lancio o meno a seconda della realizzazione]

Da 3., come std::launch::async | std::launch::deferred è l'opzione di default, significa che l'implementazione della funzione template std::async deciderà se si creerà una nuova discussione o no. Ciò è dovuto al fatto che alcune implementazioni potrebbero verificare la sovrascrittura.

ATTENZIONE

La sezione seguente non è correlato alla tua domanda, ma penso che sia importante tenere a mente.

Lo standard C++ dice che se un std::future tiene l'ultimo riferimento allo stato condiviso corrispondente a una chiamata a una funzione asincrona, distruttore che std :: del futuro deve bloccare finché il filo per le finiture funzione asincrono in esecuzione. Un'istanza di std::future restituita da std::async bloccherà quindi il suo distruttore.

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

Questo codice fuorviante può farvi pensare che i std::async chiamate sono asincrone, in realtà sono sincroni. Le istanzerestituite da std::async sono temporanee e verranno bloccate perché il loro distruttore viene chiamato a destra quando std::async restituisce come non sono assegnati a una variabile.

La prima chiamata a std::async verrà bloccata per 2 secondi, seguita da altri 2 secondi di blocco dalla seconda chiamata a std::async. Potremmo pensare che l'ultima chiamata a std::async non blocchi, poiché memorizziamo la sua istanza restituita std::future in una variabile, ma poiché si tratta di una variabile locale che viene distrutta alla fine dell'ambito, verrà effettivamente bloccata per altri 2 secondi alla fine dell'ambito della funzione, quando la variabile locale f viene distrutta.

In altre parole, la chiamata alla funzione operation() bloccherà qualsiasi thread in cui viene chiamato in sincrono per circa 6 secondi. Tali requisiti potrebbero non esistere in una versione futura dello standard C++.

Le fonti di informazione sono utilizzati per compilare queste note:

C++ concorrenza in Azione: Practical multithreading, Anthony Williams

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

+0

Giusto per chiarire, se cambi le normali chiamate di 'std :: async' in' auto t1 = std :: async (...); 'nel tuo esempio, il tempo di blocco sarà solo di circa 2 secondi, giusto? –

+0

@AdamHunyadi se intendi il tempo di blocco totale - sì, perché tre thread diversi potrebbero dormire simultaneamente. –

0

ero confuso anche da questo e ha eseguito un rapido test su Windows che mostra che il futuro asincrono verrà eseguito sui thread del pool di thread del sistema operativo. Una semplice applicazione può dimostrarlo, scoppiando in Visual Studio mostrerà anche i thread in esecuzione denominati come "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; 
} 

si tradurrà in un output simile a:

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

Questo non è vero. Aggiungi thread_local valore memorizzato e vedrete, che in realtà std::async run f1 f2 f3 attività in diversi thread, ma con lo stesso std::thread::id

Problemi correlati