Jaka jest różnica między packaged_task i async

137

Podczas pracy z wątkowym modelem C ++ 11 zauważyłem to

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';

i

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

wydają się robić dokładnie to samo. Rozumiem, że może być duża różnica, gdybym biegał std::asyncz std::launch::deferred, ale czy w tym przypadku jest taka?

Jaka jest różnica między tymi dwoma podejściami, a co ważniejsze, w jakich przypadkach użycia powinienem stosować jedno nad drugim?

nijansen
źródło

Odpowiedzi:

164

W rzeczywistości podany przykład pokazuje różnice, jeśli używasz dość długiej funkcji, takiej jak

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

Zadanie w pakiecie

packaged_taskNie rozpocznie się swoją własną, trzeba ją wywołać:

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

Z drugiej strony, std::asyncwith launch::asyncspróbuje uruchomić zadanie w innym wątku:

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";

Wada

Ale zanim spróbujesz użyć asyncdo wszystkiego, pamiętaj, że zwrócona przyszłość ma specjalny stan współdzielony, który wymaga future::~futureblokowania:

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;
*/

Więc jeśli chcesz mieć rzeczywistą asynchroniczność, musisz zachować zwracany wynik futurelub jeśli nie obchodzi Cię wynik, jeśli zmienią się okoliczności:

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

Aby uzyskać więcej informacji na ten temat, zobacz artykuł Herba Suttera asynci~future , który opisuje problem, oraz Scott Meyer std::futuresz std::asyncnie są specjalne , który opisuje spostrzeżenia. Zwróć także uwagę, że to zachowanie zostało określone w C ++ 14 i nowszych , ale jest również powszechnie zaimplementowane w C ++ 11.

Dalsze różnice

Korzystając z niego std::async, nie możesz już uruchamiać zadania na określonym wątku, skąd std::packaged_taskmożna je przenieść do innych wątków.

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";

Ponadto packaged_tasknależy wywołać a przed wywołaniem f.get(), w przeciwnym razie program zawiesi się, ponieważ przyszłość nigdy nie będzie gotowa:

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

TL; DR

Użyj, std::asyncjeśli chcesz, aby coś zostało zrobione i nie obchodzi std::packaged_taskcię, kiedy są zrobione, i jeśli chcesz podsumować rzeczy, aby przenieść je do innych wątków lub zadzwonić później. Lub cytując Christiana :

Ostatecznie a std::packaged_taskjest po prostu funkcją niższego poziomu do zaimplementowania std::async(dlatego może zdziałać więcej niż w std::asyncpołączeniu z innymi elementami niższego poziomu, takimi jak std::thread). Po prostu wymówione a std::packaged_taskjest std::functionpołączeniem z a std::futureoraz std::asynczawija i wywołuje std::packaged_task(prawdopodobnie w innym wątku).

Zeta
źródło
9
Należy dodać, że przyszłość zwracana przez bloki asynchroniczne po zniszczeniu (tak jakbyś wywołała get), podczas gdy ta zwrócona z packaged_task nie.
John5342
24
Ostatecznie a std::packaged_taskjest po prostu funkcją niższego poziomu do zaimplementowania std::async(dlatego może zdziałać więcej niż w std::asyncpołączeniu z innymi elementami niższego poziomu, takimi jak std::thread). Po prostu wymówione a std::packaged_taskjest std::functionpołączeniem z a std::futureoraz std::asynczawija i wywołuje std::packaged_task(prawdopodobnie w innym wątku).
Christian Rau
Przeprowadzam kilka eksperymentów na bloku ~ future (). Nie mogłem odtworzyć efektu blokowania przyszłego zniszczenia obiektu. Wszystko działało asynchronicznie. Używam VS 2013 i kiedy uruchamiam async, użyłem std :: launch :: async. Czy VC ++ „naprawiło” ten problem?
Frank Liu,
1
@FrankLiu: Cóż, N3451 to zaakceptowana propozycja, która (o ile wiem) trafiła do C ++ 14. Biorąc pod uwagę, że Herb pracuje w Microsoft, nie zdziwiłbym się, gdyby ta funkcja została zaimplementowana w VS2013. Kompilator, który ściśle przestrzega reguł C ++ 11, nadal wykazywałby takie zachowanie.
Zeta
1
@Mikhail Ta odpowiedź poprzedza zarówno C ++ 14, jak i C ++ 17, więc nie miałem pod ręką standardów, a jedynie propozycje. Usunę akapit.
Zeta
1

Pakowane zadanie vs async

p> Zadanie spakowane zawiera zadanie[function or function object]i parę przyszłość / obietnica. Kiedy zadanie wykonuje instrukcję return, powodujeset_value(..)topackaged_taskobietnicę.

a> Biorąc pod uwagę przyszłość, obietnicę i pakiet zadań, możemy tworzyć proste zadania, nie martwiąc się zbytnio o wątki [wątek to po prostu coś, co dajemy, aby uruchomić zadanie].

Musimy jednak rozważyć, ile wątków użyć lub czy zadanie najlepiej uruchamiać w bieżącym wątku, czy w innym itp. Takie opisy można obsłużyć za pomocą programu uruchamiającego wątki o nazwie async(), który decyduje, czy utworzyć nowy wątek, czy ponownie przetworzyć stary. jeden lub po prostu uruchom zadanie w bieżącym wątku. Zwraca przyszłość.

maxshuty
źródło
0

„Szablon klasy std :: packaged_task opakowuje dowolny możliwy do wywołania cel (funkcję, wyrażenie lambda, wyrażenie powiązania lub inny obiekt funkcji), aby można go było wywołać asynchronicznie. Wartość zwracana lub zgłoszony wyjątek jest przechowywany we wspólnym stanie, do którego można uzyskać dostęp poprzez std :: future objects. "

„Funkcja szablonu async uruchamia funkcję f asynchronicznie (potencjalnie w oddzielnym wątku) i zwraca std :: future, który ostatecznie będzie zawierał wynik tego wywołania funkcji”.

Radoslav.B
źródło