Kalau kebutuhan kita hanya “jalankan pekerjaan ini di thread lain, lalu ambil hasilnya”, menulis std::thread + promise + future secara manual terasa panjang. std::async membungkus ketiganya menjadi satu panggilan: beri tugasnya, terima std::future, panggil get() — dan hasilnya terambil dengan menunggu otomatis. Di balik kesederhanaan itu ada satu keputusan yang perlu kita pahami: launch policy — pekerjaan dijalankan segera di thread tersendiri (std::launch::async), atau ditunda sampai hasilnya benar-benar diminta (std::launch::deferred). Chapter ini menguji keduanya plus policy default, lalu menunjukkan exception yang menyeberang antar thread tanpa kode tambahan.

Launch Policy: async vs deferred #

Dua policy yang bisa dipilih:

  • std::launch::async: tugas dijalankan SEGERA di thread baru. Panggilan get() kemudian menunggu sampai selesai.
  • std::launch::deferred: tugas TIDAK dijalankan dulu; ia baru dieksekusi saat get() (atau wait()) dipanggil — dan dieksekusi di thread pemanggil, layaknya pemanggilan fungsi biasa yang ditunda.
cpp
#include <future>
#include <iostream>
#include <stdexcept>

int jumlahAngka(int n) {
    int total = 0;
    for (int i = 1; i <= n; ++i) {
        total += i;
    }
    return total;
}

int main() {
    // 1) policy default (async | deferred): keputusan diserahkan ke implementasi
    std::future<int> f0 = std::async(jumlahAngka, 10);
    std::cout << "default : " << f0.get() << "\n";

    // 2) std::launch::async: tugas dijalankan di thread tersendiri
    std::future<int> f1 = std::async(std::launch::async, jumlahAngka, 100);
    std::cout << "async   : " << f1.get() << "\n";

    // 3) std::launch::deferred: dieksekusi malas, baru jalan saat get()
    std::future<int> f2 = std::async(std::launch::deferred, jumlahAngka, 100);
    std::cout << "deferred: " << f2.get() << "\n";

    // 4) exception di dalam tugas diantar future ke pemanggil
    std::future<int> f3 = std::async(std::launch::async, []() -> int {
        throw std::runtime_error("kerja gagal");
    });
    try {
        f3.get();
    } catch (const std::exception& e) {
        std::cout << "exception: " << e.what() << "\n";
    }
    return 0;
}

Output yang Dihasilkan:

text
default : 55
async   : 5050
deferred: 5050
exception: kerja gagal

Bedah barisnya:

  1. Baris 15-16 (policy default): std::async(f, args) tanpa policy memakai std::launch::async | std::launch::deferred — implementasi bebas memilih salah satu. Nilai 55 = jumlah 1..10.
  2. Baris 19-20 (launch::async): jumlahAngka(100) mulai dihitung di thread lain SEKETIKA; get() menunggu bila belum selesai. Hasilnya 5050.
  3. Baris 23-24 (launch::deferred): sampai baris get(), tugas belum menyentuh CPU sama sekali. get() menjalankannya langsung di thread main() — hasilnya tetap 5050, tetapi tidak ada paralelisme yang terjadi.
  4. Baris 27-29 (tugas melempar exception): exception di dalam tugas async ditangkap pustaka, disimpan di dalam state bersama, dan dilempar ulang TEPAT di baris get().
  5. Baris 31-33 (penerimaan): f3.get() melempar std::runtime_error("kerja gagal") di thread main(), yang kita tangkap seperti exception biasa. Tidak ada kode pengiriman yang perlu ditulis — mirip mekanisme set_exception pada chapter future dan promise, otomatis.

Catatan Portabilitas Policy Default #

Policy default async | deferred adalah sumber kejutan portabilitas yang klasik. Beberapa implementasi memilih menjalankan tugas segera; implementasi lain (dan kondisi beban sistem tertentu) memilih menunda. Akibatnya, kode yang bergantung pada “pasti sudah berjalan sebelum baris berikutnya” bisa berperilaku berbeda antar platform — atau antar versi pustaka yang sama. Aturan praktisnya tegas:

Kelalaian yang Sering Terjadi #

Ada satu jebakan yang wajib kita kenal: std::future yang dikembalikan std::async dengan policy async memiliki destruktor khusus — bila future itu dibuang tanpa pernah di-get()/wait(), destruktor MENUNGGU tugasnya selesai. Artinya kode seperti std::async(std::launch::async, kerja); // hasil dibuang tetap memblokir satu baris penuh, dan pekerjaan yang kelihatannya berjalan paralel sebenarnya menyatu kembali seketika. Selalu simpan future yang dikembalikan std::async, dan get() saat memang dibutuhkan.

Dengan std::async, membuka pekerjaan lalu menunggu hasilnya tinggal dua baris. Namun perhatikan betapa banyak detail hidup thread yang masih harus kita urus manual — join(), sinyal penghentian. C++20 menyederhanakan semuanya dengan std::jthread dan std::stop_token, dan itu topik chapter berikutnya.

Referensi: cppreference — std::async , cppreference — std::future .