Qual è la differenza tra packaged_task e async


134

Mentre lavoravo con il modello threaded di C ++ 11, ho notato che

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';

e

auto f = std::async(std::launch::async, 
    [](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';

sembra fare esattamente la stessa cosa. Capisco che potrebbe esserci una grande differenza se corro std::asynccon std::launch::deferred, ma ce n'è uno in questo caso?

Qual è la differenza tra questi due approcci e, cosa ancora più importante, in quali casi d'uso dovrei usare l'uno rispetto all'altro?

Risposte:


161

In realtà l'esempio che hai appena mostrato mostra le differenze se usi una funzione piuttosto lunga, come

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

Compito impacchettato

A packaged_tasknon inizierà da solo, devi invocarlo:

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

D'altra parte, std::asynccon launch::asyncproverà a eseguire l'attività in un thread diverso:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

Inconveniente

Ma prima di provare a utilizzare asynctutto, tieni presente che il futuro restituito ha uno stato condiviso speciale, che richiede che future::~futureblocchi:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/

Quindi, se vuoi un vero asincrono, devi conservare il reso futureo se non ti interessa il risultato se le circostanze cambiano:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

Per ulteriori informazioni su questo, vedi l'articolo di Herb Sutter asynce~future , che descrive il problema, e Scott Meyer's std::futuresdi std::asyncnon sono speciali , che descrive le intuizioni. Si noti inoltre che questo comportamento è stato specificato in C ++ 14 e versioni successive , ma anche comunemente implementato in C ++ 11.

Ulteriori differenze

Usando std::asyncnon è più possibile eseguire l'attività su un thread specifico, dove std::packaged_taskpuò essere spostato su altri thread.

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";

Inoltre, è packaged_tasknecessario invocare un messaggio prima di chiamare f.get(), altrimenti il ​​programma si bloccherà poiché il futuro non sarà mai pronto:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL; DR

Usa std::asyncse vuoi che alcune cose vengano fatte e che non ti interessino davvero quando sono fatte, e std::packaged_taskse vuoi concludere le cose per spostarle su altri thread o chiamarle in seguito. Oppure, per citare Christian :

Alla fine a std::packaged_taskè solo una funzionalità di livello inferiore per l'implementazione std::async(motivo per cui può fare di più che std::asyncse usata insieme ad altre cose di livello inferiore, come std::thread). Semplicemente detto a std::packaged_taskè un std::functioncollegato a std::futuree std::asyncavvolge e chiama a std::packaged_task(possibilmente in un thread diverso).


9
Dovresti aggiungere che il futuro restituito dai blocchi asincroni alla distruzione (come se avessi chiamato get) mentre quello restituito da packaged_task no.
Giovanni 5342

22
Alla fine a std::packaged_taskè solo una funzionalità di livello inferiore per l'implementazione std::async(motivo per cui può fare di più che std::asyncse usata insieme ad altre cose di livello inferiore, come std::thread). Semplicemente detto a std::packaged_taskè un std::functioncollegato a std::futuree std::asyncavvolge e chiama a std::packaged_task(possibilmente in un thread diverso).
Christian Rau,

Sto facendo alcuni esperimenti sul blocco ~ future (). Non ho potuto replicare l'effetto di blocco sulla distruzione futura degli oggetti. Tutto ha funzionato in modo asincrono. Sto usando VS 2013 e quando lancio asincrono, ho usato std :: launch :: async. VC ++ in qualche modo "risolve" questo problema?
Frank Liu,

1
@FrankLiu: Beh, N3451 è una proposta accettata, che (per quanto ne so) è entrata in C ++ 14. Dato che Herb lavora in Microsoft, non sarei sorpreso se questa funzionalità fosse implementata in VS2013. Un compilatore che segue rigorosamente le regole C ++ 11 mostrerebbe comunque questo comportamento.
Zeta,

1
@Mikhail Questa risposta precede sia C ++ 14 che C ++ 17, quindi non avevo gli standard ma solo proposte a portata di mano. Rimuoverò il paragrafo.
Zeta,

1

Compito impacchettato vs asincrono

p> L' attività in pacchetto contiene un'attività[function or function object]e una coppia futuro / promessa. Quando l'attività esegue un'istruzione return, causaset_value(..)lapackaged_taskpromessa.

a> Dato compito Future, promessa e pacchetto possiamo creare compiti semplici senza preoccuparci troppo dei thread [il thread è solo qualcosa che diamo per eseguire un'attività].

Tuttavia, dobbiamo considerare quanti thread utilizzare o se un'attività deve essere eseguita meglio sul thread corrente o su un altro. Tali descrizioni possono essere gestite da un lanciatore di thread chiamato async(), che decide se creare un nuovo thread o riciclare un vecchio uno o semplicemente eseguire l'attività sul thread corrente. Restituisce un futuro.


0

"Il modello di classe std :: packaged_task avvolge qualsiasi destinazione richiamabile (funzione, espressione lambda, espressione di bind o altro oggetto funzione) in modo che possa essere invocata in modo asincrono. Il suo valore di ritorno o eccezione generata viene archiviato in uno stato condiviso a cui è possibile accedere tramite std :: oggetti futuri. "

"La funzione template async esegue la funzione f in modo asincrono (potenzialmente in un thread separato) e restituisce uno std :: future che alla fine conterrà il risultato di quella chiamata di funzione."

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.