Dua kelemahan std::thread yang sudah kita alami di chapter awal: lupa join() berakibat std::terminate, dan tidak ada cara standar untuk berkata “silakan berhenti” kepada thread yang sedang bekerja. C++20 menjawab keduanya dengan std::jthread (joining thread): join dilakukan otomatis di destructor, dan setiap jthread membawa mekanisme penghentian kooperatif bawaan — std::stop_token di sisi pekerja, std::stop_source di sisi pemanggil. Pekerja tetap yang memutus KAPAN berhenti (dengan memeriksa sinyal di titik yang aman), dan itulah makna kata kooperatif. Chapter ini menguji semuanya dalam satu program — dengan keluaran yang memang tidak sepenuhnya bisa kita kendalikan.

jthread: join Otomatis di Destructor #

Konstruktor std::jthread identik dengan std::thread. Perbedaannya ada di ujung umur: destructor memeriksa joinable() dan, bila perlu, memanggil request_stop() lalu join() sebelum objek berakhir. Kategori kecelakaan “lupa join” pun hilang dari daftar masalah. Tambahan lain yang halus: lambda pekerja boleh menerima std::stop_token sebagai parameter pertama — bila ada, pustaka otomatis menghubungkannya dengan sinyal jthread tersebut.

Penghentian Kooperatif: stop_token, stop_source, stop_callback #

Mekanismenya terdiri dari tiga bagian:

  • std::stop_source (di sisi pemanggil): diperoleh dari jthread.get_stop_source(); request_stop() memancarkan sinyal penghentian.
  • std::stop_token (di sisi pekerja): stop_requested() menanyakan apakah sinyal sudah datang. Pekerja memeriksanya secara berkala di titik yang aman — bukan digagalkan paksa di tengah instruksi.
  • std::stop_callback: fungsi yang dijalankan SEKETIKA saat sinyal dipancarkan, untuk reaksi seperti menutup koneksi atau menandai log.
cpp
#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;

int main() {
    bool pekerjaSelesai = false;

    {
        // jthread menerima std::stop_token sebagai parameter pertama
        std::jthread pekerja([&pekerjaSelesai](std::stop_token token) {
            // callback dijalankan saat sinyal penghentian dipancarkan
            std::stop_callback sinyal(token, [] {
                std::cout << "stop_callback: sinyal diterima\n";
            });

            while (!token.stop_requested()) {   // kooperatif: cek tiap putaran
                std::this_thread::yield();
            }
            pekerjaSelesai = true;   // dibaca main setelah join -> aman
        });

        std::stop_source sumber = pekerja.get_stop_source();
        std::cout << std::boolalpha;
        std::cout << "sebelum sinyal : stop_requested = "
                  << sumber.stop_requested() << "\n";

        std::this_thread::sleep_for(10ms);
        sumber.request_stop();   // minta berhenti lewat stop_source
    }   // akhir scope: destructor jthread otomatis join (tanpa join eksplisit)

    std::cout << "pekerja selesai: " << pekerjaSelesai << "\n";
    std::cout << "tanpa join() eksplisit: jthread mengurus sendiri\n";
    return 0;
}#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;

int main() {
    bool pekerjaSelesai = false;

    {
        // jthread menerima std::stop_token sebagai parameter pertama
        std::jthread pekerja([&pekerjaSelesai](std::stop_token token) {
            // callback dijalankan saat sinyal penghentian dipancarkan
            std::stop_callback sinyal(token, [] {
                std::cout << "stop_callback: sinyal diterima\n";
            });

            while (!token.stop_requested()) {   // kooperatif: cek tiap putaran
                std::this_thread::yield();
            }
            pekerjaSelesai = true;   // dibaca main setelah join -> aman
        });

        std::stop_source sumber = pekerja.get_stop_source();
        std::cout << std::boolalpha;
        std::cout << "sebelum sinyal : stop_requested = "
                  << sumber.stop_requested() << "\n";

        std::this_thread::sleep_for(10ms);
        sumber.request_stop();   // minta berhenti lewat stop_source
    }   // akhir scope: destructor jthread otomatis join (tanpa join eksplisit)

    std::cout << "pekerja selesai: " << pekerjaSelesai << "\n";
    std::cout << "tanpa join() eksplisit: jthread mengurus sendiri\n";
    return 0;
}sebelum sinyal : stop_requested = false
stop_callback: sinyal diterima
pekerja selesai: true
tanpa join() eksplisit: jthread mengurus sendirisebelum sinyal: stop_requested = false
pekerja berhenti secara kooperatif
stop_callback: sinyal diterima
iterasi tercatat: 46605

**PENTING**: keluaran di atas hanya representatif. Baris pertama selalu tetap (dicetak `main()` sebelum sinyal), dan baris terakhir selalu paling bawah (dicetak setelah destructor jthread selesai bergabung). Tetapi dua baris tengah bisa bertukar urutan — `stop_callback` dijalankan oleh thread pemanggil `request_stop()` (yaitu `main()`), sementara pesan "pekerja berhenti" dicetak oleh thread pekerja yang baru menyadari sinyalnya; keduanya saling berlomba. Angka `iterasi tercatat` juga pasti berbeda setiap eksekusi: jumlah putaran bergantung pada berapa lama pekerja mendapat jatah CPU dalam kurun 10 milidetik. Yang dijamin hanyalah nilainya lebih dari nol dan pencetakannya aman (dilakukan setelah join otomatis).

Bedah barisnya:

1. **Baris 12 (lambda dengan `std::stop_token token`)**: parameter pertama bertipe `stop_token` membuat pustaka otomatis menyambungkan token dengan sinyal jthread ini — tanpa kode penghubung apa pun.
2. **Baris 14-16 (`stop_callback`)**: callback terdaftar selama pekerja bekerja. Saat `request_stop()` dipanggil, ia dijalankan seketika oleh thread pemanggil.
3. **Baris 18-21 (loop kooperatif)**: pekerja terus bekerja selama `stop_requested()` masih `false`. `yield()` menyerahkan giliran CPU agar loop tidak mendominasi core.
4. **Baris 25 (`get_stop_source()`)**: `main()` memegang kendali penghentian. `stop_requested()` di baris 27-28 masih `false` karena sinyal belum dipancarkan.
5. **Baris 31 (`sumber.request_stop()`)**: sinyal dipancarkan — `stop_callback` dieksekusi, dan pada pemeriksaan berikutnya pekerja melihat `stop_requested() == true`.
6. **Baris 32 (akhir scope)**: destructor `jthread` menunggu pekerja benar-benar selesai. Tidak ada `join()` eksplisit, dan `iterasi` dibaca aman setelahnya.

## Kenapa Kooperatif, Bukan Paksa

Desain ini sengaja tidak menyediakan tombol "hentikan paksa". Menghentikan thread di tengah instruksi bisa meninggalkan kunci terpasang, data setengah jadi, dan resource yang tidak pernah dilepas — kondisi yang tidak bisa ditangkap exception. Penghentian kooperatif membalik tanggung jawab: pemanggil menyatakan NIAT, pekerja memilih titik berhenti yang aman. Pola umumnya: periksa `stop_requested()` di setiap iterasi loop besar, atau lewatkan `stop_token` ke operasi penunggu seperti `condition_variable::wait` dan `future::wait_for` yang punya varian sadar-stop (C++20).

> [!TIP]
> `std::stop_token` tidak terikat pada jthread saja — ia bisa disalin dan diteruskan ke fungsi mana pun, sehingga satu sumber sinyal bisa menghentikan banyak pekerja sekaligus. Gunakan ini sebagai fondasi pembatalan tugas (cancellation) di aplikasi nyata.

Kita kini punya perangkat sinkronisasi lengkap: thread, kunci, condition variable, atomic, kanal future, dan penghentian kooperatif. Tinggal satu kelas alat yang belum disentuh: mengirim SELURUH ALGORITMA ke banyak core sekaligus. Chapter berikutnya membahas execution policy pada algoritma standar — dan alternatif manualnya yang bisa langsung kita uji.

Referensi: [cppreference — std::jthread](https://en.cppreference.com/w/cpp/thread/jthread), [cppreference — std::stop_token](https://en.cppreference.com/w/cpp/thread/stop_token), [cppreference — std::stop_callback](https://en.cppreference.com/w/cpp/thread/stop_callback).