Topik wawancara representatif

Wawancara C++: Mengimplementasikan pembatalan kooperatif dengan std::jthread dan stop_token

CodingSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Implementasikan worker C++20 yang mengonsumsi tugas dalam sebuah loop sementara thread utama dapat meminta stop. Worker tidak boleh kehilangan tugas yang sudah dimulai, memblokir selamanya, atau mengakses shared state setelah destruksi. Jelaskan std::jthread, stop_token, condition_variable_any, exception, dan pengujian.

Prompt dan konteks

Implementasikan background worker yang mengonsumsi sebuah antrean. Selama shutdown atau pembatalan upstream, thread utama meminta stop; tugas yang sudah dimulai diselesaikan, sementara pekerjaan yang belum dimulai disimpan atau dibuang secara eksplisit. Hindari busy waiting, data race, thread yang masih hidup selama destruksi, dan condition-variable wait yang tidak pernah kembali.

C++20 std::jthread meminta stop dan melakukan join selama destruksi, serta dapat menginjeksikan std::stop_token ke dalam fungsi entri. Permintaan stop bersifat kooperatif; permintaan ini tidak dapat menghentikan kode arbitrer secara paksa. Worker harus melakukan polling atau menunggu dengan primitif yang sadar-stop (stop-aware).

Apa yang dievaluasi oleh pewawancara

  • Anda memahami bahwa stop token adalah permintaan pada shared state, bukan penghentian thread secara asinkron.
  • Anda menggunakan automatic joining dari std::jthread dan menjaga anggota objek tetap hidup saat thread mengaksesnya.
  • Anda membuat blocking wait dapat diinterupsi, misalnya dengan overload stop-token dari condition_variable_any.
  • Anda menjaga masa hidup (lifetime) antrean, stop state, exception tugas, dan cleanup tetap aman.
  • Anda menguji stop pada antrean kosong, stop race, exception tugas, request_stop berulang, dan urutan destruksi.

Pertanyaan untuk diklarifikasi terlebih dahulu

  • Apakah tugas yang sedang berjalan boleh selesai, dan apakah tugas tersebut idempoten atau memiliki efek samping eksternal?
  • Saat antrean ditutup, apakah tugas yang belum dikonsumsi dibuang, ditransfer, atau dikuras (drained) oleh worker lain?
  • Apakah proses wait dapat diinterupsi, atau bergantung pada panggilan I/O pihak ketiga yang tidak dapat dibatalkan?
  • Apakah exception dicatat, dipromosikan ke thread yang melakukan join, atau digunakan untuk menghentikan layanan?
  • Bisakah beberapa thread memanggil stop, destruksi, atau restart?

Jawaban 30 detik

“Miliki worker dengan std::jthread dan terima std::stop_token dalam fungsi entrinya. Lindungi antrean dengan mutex dan tunggu menggunakan condition_variable_any yang sadar-stop; setelah bangun, periksa stop, penutupan antrean, dan status tugas. Tugas yang di-dequeue berjalan hingga titik pembatalan yang ditentukan dan melakukan cleanup. Destruksi meminta stop dan melakukan join; tidak pernah melakukan detach. Shared state hidup lebih lama dari thread. Pengujian mencakup stop race dan exception.”

Solusi langkah demi langkah

Langkah 1: Tentukan kontrak pembatalan

Pisahkan “stop requested” dari “task completed”. Permintaan stop mencegah pekerjaan baru; tugas yang sudah dimulai diselesaikan atau mengembalikan hasil pembatalan pada titik-titik aman. Jangan menjanjikan pembatalan langsung untuk panggilan pihak ketiga yang arbitrer.

Langkah 2: Tentukan kepemilikan

Jaga agar objek yang memiliki antrean, mutex, condition variable, dan std::jthread tetap hidup lebih lama dari thread. Destruksi meminta stop dan menunggu sebelum melepaskan anggota. Jangan pernah menangkap referensi ke scope yang sudah mati atau mempublikasikan this mentah ke callback yang terlambat.

Langkah 3: Buat proses wait dapat dibatalkan

Gunakan wait stop-token condition_variable_any atau daftarkan stop_callback yang memanggil notify_all. Predikat memeriksa antrean tidak kosong, status ditutup, dan stop_requested(); saat bangun, peroleh kembali lock dan periksa ulang status.

cpp
std::jthread worker([this](std::stop_token st) {
  for (;;) {
    Task task;
    {
      std::unique_lock lock(mu_);
      cv_.wait(lock, st, [this, &st] {
        return closed_ || !queue_.empty() || st.stop_requested();
      });
      if (st.stop_requested() || (closed_ && queue_.empty())) return;
      task = std::move(queue_.front());
      queue_.pop_front();
    }
    run(task, st);
  }
});

Langkah 4: Tangani titik stop tugas dan exception

Periksa token di antara fase-fase tugas dan tentukan batas efek samping untuk menghindari penulisan parsial. Tangkap exception di batas thread, catat ID tugas beserta error, dan putuskan apakah akan melanjutkan atau menghentikan worker. Jangan biarkan exception keluar dari fungsi entri.

Langkah 5: Tutup dengan urutan yang benar

Tolak tugas baru, tandai antrean ditutup, beri tahu waiter, minta stop, join, dan baru setelah itu lepaskan sumber daya. Tentukan batas waktu pengurasan (drain timeout) dan penanganan tugas yang tersisa. Pemanggilan request_stop() yang berulang harus aman dan tidak boleh mengulang efek samping.

Langkah 6: Uji race condition dan amati perilaku

Uji stop pada antrean kosong, stop saat dequeue, panggilan stop simultan, notifikasi saat destruksi, exception tugas, timeout blocking-I/O, dan penutupan berulang. Catat latensi stop, tugas yang selesai/dibatalkan, antrean yang tersisa, exception, dan waktu join; jalankan ThreadSanitizer untuk mendeteksi race.

Contoh jawaban yang kuat

“Saya mengelola masa hidup worker dengan jthread dan menerima stoptoken. Mutex melindungi status antrean; conditionvariable_any yang sadar-stop memeriksa status tertutup, tidak kosong, dan permintaan stop, sehingga stop dapat membangunkan wait. Setelah dequeue, saya melepaskan lock. Tugas memeriksa token pada titik-titik aman dan menyelesaikan cleanup transaksi. Fungsi entri menangkap dan mencatat exception.”

“Shutdown menolak pekerjaan baru, menyetel closed, memberi notifikasi, meminta stop, dan melakukan join. Shutdown tidak pernah melakukan detach atau melepaskan antrean dan logger terlalu dini. Pengujian mencakup antrean kosong, race pada dequeue, exception, stop berulang, timeout tugas panjang, dan ThreadSanitizer.”

Kesalahan umum

  • Memperlakukan stop_token sebagai penghentian paksa → sumber daya dan transaksi rusak → tentukan titik-titik kooperatif.
  • Lupa melakukan join pada std::thread terminasi atau dangling thread → gunakan jthread atau kepemilikan masa hidup eksplisit.
  • Menunggu satu notifikasi saja → notifikasi yang terlewat membuat thread tidur selamanya → gunakan loop predikat dan bangun saat stop.
  • Menjalankan tugas sambil menahan lock → producer dan shutdown terblokir → lepaskan lock setelah dequeue.
  • Membiarkan exception keluar dari entri → proses berhenti → tangkap di batas thread.
  • Melepaskan anggota sebelum menghentikan thread → use-after-free → stop, join, lalu lepaskan state.

Pertanyaan lanjutan dan jawaban

Apa yang dilakukan destruktor jthread?

Jika joinable, destruksi meminta stop dan melakukan join; ini tidak menghentikan tugas secara paksa. Tugas harus merespons, sehingga join mungkin menunggu titik yang aman.

Bisakah permintaan stop membangunkan condition variable?

Overload stop-token dari condition_variable_any kembali ketika stop diminta. Wait kustom memerlukan stop callback untuk memberi tahu dan predikat yang memeriksa status lagi.

Bisakah penulisan database yang sedang berlangsung dibatalkan secara instan?

Jangan berasumsi demikian. Gunakan langkah-langkah yang dapat di-rollback atau idempoten, dukungan timeout/pembatalan driver, dan pemeriksaan stop pada batas commit.

Bagaimana Anda menghindari stop race?

Perlakukan status closed, antrean, dan stop sebagai satu protokol lifecycle. Lakukan transisi di bawah perlindungan lock dan beri tahu setelah perubahan status; uji beban pada window tempat stop dan dequeue terjadi bersamaan dan jalankan ThreadSanitizer.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Tangkapan Layar untuk perintah coding

Ambil tangkapan layar soal, lalu telusuri batasan, solusi, kode, edge case, dan kompleksitas secara berurutan.

Lihat alat