Apa perbedaan antara packaged_task dan async

134

Saat bekerja dengan model berulir C ++ 11, saya perhatikan itu

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

dan

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

tampaknya melakukan hal yang persis sama. Saya mengerti bahwa mungkin ada perbedaan besar jika saya berlari std::asyncdengan std::launch::deferred, tetapi apakah ada satu dalam kasus ini?

Apa perbedaan antara kedua pendekatan ini, dan yang lebih penting, dalam kasus penggunaan apa yang harus saya gunakan satu lebih dari yang lain?

nijansen
sumber

Jawaban:

161

Sebenarnya contoh yang baru saja Anda berikan menunjukkan perbedaan jika Anda menggunakan fungsi yang agak panjang, seperti

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

Tugas yang dikemas

A packaged_tasktidak akan memulai sendiri, Anda harus memohonnya:

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

Di sisi lain, std::asyncdengan launch::asyncakan mencoba menjalankan tugas di utas berbeda:

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

Kekurangan

Tetapi sebelum Anda mencoba menggunakan asyncuntuk semuanya, perlu diingat bahwa masa depan yang dikembalikan memiliki keadaan bersama yang khusus, yang menuntut yang future::~futuremenghalangi:

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

Jadi, jika Anda ingin sinkron nyata, Anda harus tetap mengembalikannya future, atau jika Anda tidak peduli dengan hasilnya jika keadaan berubah:

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

Untuk informasi lebih lanjut tentang ini, lihat artikel Herb Sutter asyncdan~future , yang menjelaskan masalahnya, dan Scott Meyer std::futuresdari std::asynctidak khusus , yang menjelaskan wawasan. Perhatikan juga bahwa perilaku ini ditentukan dalam C ++ 14 dan lebih tinggi , tetapi juga umum diimplementasikan dalam C ++ 11.

Perbedaan lebih lanjut

Dengan menggunakan std::asyncAnda tidak dapat menjalankan tugas Anda pada utas tertentu lagi, di mana std::packaged_taskdapat dipindahkan ke utas lainnya.

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

Juga, packaged_taskperlu dipanggil sebelum Anda menelepon f.get(), jika tidak, program Anda akan macet karena masa depan tidak akan pernah siap:

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

TL; DR

Gunakan std::asyncjika Anda ingin beberapa hal selesai dan tidak benar-benar peduli ketika sudah selesai, dan std::packaged_taskjika Anda ingin menyelesaikannya untuk memindahkannya ke utas lain atau hubungi mereka nanti. Atau, mengutip Christian :

Pada akhirnya a std::packaged_taskhanyalah fitur level yang lebih rendah untuk implementasi std::async(itulah sebabnya ia dapat melakukan lebih banyak daripada std::asyncjika digunakan bersama dengan hal-hal level rendah lainnya, seperti std::thread). Secara sederhana a std::packaged_taskadalah std::functiontautan ke a std::futuredan std::asyncmembungkus dan memanggil a std::packaged_task(mungkin di utas yang berbeda).

Zeta
sumber
9
Anda harus menambahkan bahwa masa depan dikembalikan oleh blok async pada penghancuran (seolah-olah Anda dipanggil get) sedangkan yang dikembalikan dari packaged_task tidak.
John5342
22
Pada akhirnya a std::packaged_taskhanyalah fitur level yang lebih rendah untuk implementasi std::async(itulah sebabnya ia dapat melakukan lebih banyak daripada std::asyncjika digunakan bersama dengan hal-hal level rendah lainnya, seperti std::thread). Secara sederhana a std::packaged_taskadalah std::functiontautan ke a std::futuredan std::asyncmembungkus dan memanggil a std::packaged_task(mungkin di utas yang berbeda).
Christian Rau
Saya sedang melakukan beberapa percobaan di blok ~ future (). Saya tidak bisa meniru efek pemblokiran pada penghancuran objek di masa depan. Semuanya bekerja secara sinkron. Saya menggunakan VS 2013 dan ketika saya meluncurkan async, saya menggunakan std :: launch :: async. Apakah VC ++ entah bagaimana "memperbaiki" masalah ini?
Frank Liu
1
@ FrankLiu: Ya, N3451 adalah proposal yang diterima, yang (sejauh yang saya tahu) masuk ke C ++ 14. Mengingat bahwa Herb berfungsi di Microsoft, saya tidak akan terkejut jika fitur itu diterapkan di VS2013. Kompiler yang secara ketat mengikuti aturan C ++ 11 masih akan menunjukkan perilaku ini.
Zeta
1
@Mikhail Jawaban ini mendahului C ++ 14 dan C ++ 17, jadi saya tidak memiliki standar tetapi hanya proposal yang ada. Saya akan menghapus paragraf.
Zeta
1

Paket Tugas vs async

p> Tugas terpaket memiliki tugas[function or function object]dan pasangan masa depan / janji. Saat tugas mengeksekusi pernyataan kembali, hal itu menyebabkanset_value(..)padapackaged_task's janji.

a> Diberikan Masa Depan, janji dan tugas paket kita dapat membuat tugas-tugas sederhana tanpa terlalu khawatir tentang utas [utas hanyalah sesuatu yang kita berikan untuk menjalankan tugas].

Namun kita perlu mempertimbangkan berapa banyak utas untuk digunakan atau apakah tugas paling baik dijalankan pada utas saat ini atau pada yang lain dll. Keputusan semacam itu dapat ditangani oleh peluncur utas yang disebut async(), yang memutuskan apakah akan membuat utas baru atau mendaur ulang yang lama satu atau cukup jalankan tugas di utas saat ini. Ia mengembalikan masa depan.

Maxshuty
sumber
0

"Templat kelas std :: packaged_task membungkus target yang dapat dipanggil (fungsi, ekspresi lambda, ekspresi bind, atau objek fungsi lainnya) sehingga dapat dipanggil secara tidak sinkron. Nilai balik atau pengecualian yang dilemparkan disimpan dalam keadaan bersama yang dapat diakses melalui std :: objek masa depan. "

"Fungsi templat async menjalankan fungsi f asinkron (berpotensi di utas terpisah) dan mengembalikan std :: future yang pada akhirnya akan menampung hasil dari pemanggilan fungsi itu."

Radoslav.B
sumber