Topik temu duga representatif

Temu duga C++: Laksanakan pembatalan koperatif dengan std::jthread dan stop_token

PengekodanSukar
Pasukan Editorial Offer.ccDiterbitkan Dikemas kini

Soalan

Laksanakan pekerja C++20 yang menggunakan tugas dalam gelung sementara benang utama boleh meminta henti. Ia tidak boleh kehilangan tugas yang telah dimulakan, menyekat selama-lamanya, atau mengakses keadaan dikongsi selepas pemusnahan. Terangkan std::jthread, stop_token, condition_variable_any, pengecualian, dan ujian.

Prompt dan konteks

Laksanakan pekerja latar belakang yang menggunakan baris gilir. Semasa penutupan atau pembatalan huluan, benang utama meminta henti; tugas yang telah dimulakan diselesaikan, manakala kerja yang belum dimulakan sama ada dikekalkan atau dibuang secara eksplisit. Elakkan menunggu sibuk (busy waiting), perlumbaan data, benang hidup semasa pemusnahan, dan penungguan pemboleh ubah keadaan yang tidak pernah kembali.

C++20 std::jthread meminta henti dan menyertai (join) semasa pemusnahan, dan boleh menyuntik std::stop_token ke dalam fungsi masuk. Permintaan henti adalah bersifat koperatif; ia tidak boleh menamatkan kod sewenang-wenangnya secara paksa. Pekerja mesti meninjau (poll) atau menunggu dengan primitif yang peka terhadap henti.

Perkara yang dinilai oleh penemu duga

  • Anda memahami bahawa token henti ialah permintaan pada keadaan dikongsi, bukan pembunuhan benang tak segerak.
  • Anda menggunakan penyertaan automatik std::jthread dan mengekalkan ahli objek hidup semasa benang mengaksesnya.
  • Anda membuat penungguan menyekat boleh diganggu, contohnya dengan beban lampau stop-token condition_variable_any.
  • Anda memastikan jangka hayat baris gilir, keadaan henti, pengecualian tugas dan pembersihan selamat.
  • Anda menguji henti baris gilir kosong, perlumbaan henti, pengecualian tugas, request_stop berulang, dan susunan pemusnahan.

Soalan untuk dijelaskan terlebih dahulu

  • Bolehkah tugas yang sedang berjalan diselesaikan, dan adakah ia idempoten atau mempunyai kesan sampingan luaran?
  • Apabila baris gilir ditutup, adakah tugas yang belum digunakan digugurkan, dipindahkan, atau disalirkan oleh pekerja lain?
  • Adakah penungguan boleh diganggu, atau adakah ia bergantung pada panggilan I/O pihak ketiga yang tidak boleh dibatalkan?
  • Adakah pengecualian direkodkan, disebarkan ke benang penyertaan, atau digunakan untuk menghentikan perkhidmatan?
  • Bolehkah berbilang benang memanggil henti, pemusnahan, atau mula semula?

Jawapan 30 saat

“Miliki pekerja dengan std::jthread dan terima std::stop_token dalam fungsi masuknya. Lindungi baris gilir dengan muteks dan tunggu menggunakan condition_variable_any yang peka henti; selepas bangun periksa henti, penutupan baris gilir, dan keadaan tugas. Tugas yang dinyahbaris gilir berjalan ke titik pembatalan yang ditentukan dan melakukan pembersihan. Pemusnahan meminta henti dan menyertai (join); ia tidak pernah menanggalkan (detach). Keadaan dikongsi hidup lebih lama daripada benang. Ujian merangkumi perlumbaan henti dan pengecualian.”

Penyelesaian langkah demi langkah

Langkah 1: Tentukan kontrak pembatalan

Asingkan "henti diminta" daripada "tugas selesai." Permintaan henti menghalang kerja baharu; tugas yang telah dimulakan selesai atau mengembalikan hasil pembatalan pada titik selamat. Jangan menjanjikan pembatalan serta-merta bagi panggilan pihak ketiga sewenang-wenangnya.

Langkah 2: Tentukan pemilikan

Pastikan objek yang memiliki baris gilir, muteks, pemboleh ubah keadaan dan std::jthread hidup lebih lama daripada benang. Pemusnahan meminta henti dan menunggu sebelum melepaskan ahli. Jangan sekali-kali menangkap rujukan kepada skop yang telah mati atau menerbitkan this mentah kepada panggilan balik yang lewat.

Langkah 3: Jadikan penungguan boleh dibatalkan

Gunakan penungguan stop-token condition_variable_any atau daftarkan stop_callback yang memanggil notify_all. Predikat memeriksa baris gilir tidak kosong, keadaan ditutup, dan stop_requested(); apabila terjaga, peroleh semula kunci dan periksa semula keadaan.

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: Kendalikan titik henti tugas dan pengecualian

Periksa token antara fasa tugas dan tentukan sempadan kesan sampingan untuk mengelakkan penulisan separa. Tangkap pengecualian pada sempadan benang, rekod ID tugas dan ralat, dan putuskan sama ada untuk meneruskan atau menghentikan pekerja. Jangan biarkan pengecualian terlepas daripada fungsi masuk.

Langkah 5: Tutup dalam susunan yang betul

Tolak tugas baharu, tandakan baris gilir ditutup, maklumkan penunggu, minta henti, sertai (join), dan hanya selepas itu lepaskan sumber. Tentukan tamat masa penyaliran (drain timeout) dan pengendalian tugas yang tertinggal. request_stop() yang berulang mestilah selamat dan tidak boleh mengulangi kesan sampingan.

Langkah 6: Uji perlumbaan dan perhatikan tingkah laku

Uji henti baris gilir kosong, henti semasa menyahbaris gilir, panggilan henti serentak, pemberitahuan semasa pemusnahan, pengecualian tugas, tamat masa I/O menyekat, dan penutupan berulang. Rekod kependaman henti, tugas selesai/dibatalkan, baki baris gilir, pengecualian, dan masa penyertaan; jalankan ThreadSanitizer untuk mengesan perlumbaan.

Contoh jawapan yang mantap

“Saya menguruskan jangka hayat pekerja dengan jthread dan menerima stoptoken. Muteks melindungi keadaan baris gilir; conditionvariable_any yang peka henti memeriksa keadaan ditutup, tidak kosong dan permintaan henti, jadi henti membangunkan penungguan. Selepas menyahbaris gilir, saya melepaskan kunci. Tugas memeriksa token pada titik selamat dan menyelesaikan pembersihan transaksi. Fungsi masuk menangkap dan merekodkan pengecualian.”

“Penutupan menolak kerja baharu, menetapkan ditutup, memberitahu, meminta henti dan menyertai (join). Ia tidak pernah menanggalkan (detach) atau melepaskan baris gilir dan pencatat (logger) lebih awal. Ujian merangkumi baris gilir kosong, perlumbaan nyahbaris gilir, pengecualian, henti berulang, tamat masa tugas panjang dan ThreadSanitizer.”

Kesilapan biasa

  • Menganggap stop_token sebagai pembunuhan paksa → sumber dan transaksi rosak → tentukan titik koperatif.
  • Lupa untuk menyertai (join) std::thread penamatan atau benang tergantung → gunakan jthread atau pemilikan jangka hayat eksplisit.
  • Menunggu satu pemberitahuan sahaja → pemberitahuan yang terlepas tidur selama-lamanya → gunakan gelung predikat dan bangun apabila henti.
  • Menjalankan tugas semasa memegang kunci → pengeluar dan penutupan tersekat → lepaskan selepas menyahbaris gilir.
  • Membiarkan pengecualian terlepas daripada fungsi masuk → proses ditamatkan → tangkap pada sempadan benang.
  • Melepaskan ahli sebelum menghentikan benang → guna-selepas-bebas (use-after-free) → henti, sertai (join), kemudian lepaskan keadaan.

Soalan susulan dan jawapan

Apakah yang dilakukan oleh pemusnah jthread?

Jika ia boleh disertai (joinable), pemusnahan meminta henti dan menyertai (join); ia tidak menamatkan tugas secara paksa. Tugas mesti bertindak balas, jadi penyertaan mungkin menunggu titik selamat.

Bolehkah permintaan henti membangunkan pemboleh ubah keadaan?

Beban lampau stop-token condition_variable_any kembali apabila henti diminta. Penungguan tersuai memerlukan panggilan balik henti untuk memberitahu dan predikat yang memeriksa keadaan semula.

Bolehkah penulisan pangkalan data dalam penerbangan dibatalkan serta-merta?

Jangan anggap begitu. Gunakan langkah yang boleh diundur (rollback) atau idempoten, sokongan tamat masa/pembatalan pemacu, dan pemeriksaan henti pada sempadan komit.

Bagaimanakah anda mengelakkan perlumbaan henti?

Anggap ditutup, baris gilir, dan henti sebagai satu protokol kitaran hayat. Lakukan peralihan di bawah kunci dan maklumkan selepas perubahan keadaan; beri tekanan pada tetingkap di mana henti dan nyahbaris gilir berlaku bersama dan jalankan ThreadSanitizer.

Sumber awam

Soalan berkaitan

Alat temu duga berkaitan

Gunakan Tangkapan Skrin untuk gesaan pengekodan

Tangkap soalan, kemudian selesaikan kekangan, penyelesaian, kod, kes pinggir dan kerumitan mengikut urutan.

Lihat alat