Kami memiliki fungsi yang dipanggil oleh satu utas (kami beri nama ini utas utama). Di dalam tubuh fungsi kita menelurkan beberapa utas pekerja untuk melakukan pekerjaan intensif CPU, tunggu sampai semua utas selesai, lalu kembalikan hasilnya ke utas utama.
Hasilnya adalah pemanggil dapat menggunakan fungsi tersebut secara naif, dan secara internal akan menggunakan beberapa inti.
Sejauh ini semuanya baik-baik saja ..
Masalah yang kami hadapi adalah tentang pengecualian. Kami tidak ingin pengecualian pada utas pekerja membuat aplikasi mogok. Kami ingin pemanggil ke fungsi tersebut dapat menangkap mereka di utas utama. Kita harus menangkap pengecualian pada utas pekerja dan menyebarkannya ke utas utama agar mereka terus melepaskannya dari sana.
Bagaimana kita bisa melakukan ini?
Yang terbaik yang bisa saya pikirkan adalah:
- Tangkap berbagai macam pengecualian pada utas pekerja kita (std :: exception dan beberapa milik kita sendiri).
- Catat jenis dan pesan pengecualiannya.
- Memiliki pernyataan sakelar yang sesuai di utas utama yang memunculkan kembali pengecualian dari jenis apa pun yang direkam di utas pekerja.
Ini memiliki kerugian yang jelas karena hanya mendukung serangkaian jenis pengecualian terbatas, dan akan memerlukan modifikasi setiap kali jenis pengecualian baru ditambahkan.
sumber
Saat ini, satu-satunya cara portabel adalah menulis klausa catch untuk semua jenis pengecualian yang mungkin ingin Anda transfer antar utas, menyimpan informasi di suatu tempat dari klausa catch tersebut dan kemudian menggunakannya nanti untuk melempar ulang pengecualian. Ini adalah pendekatan yang diambil oleh Boost.Exception .
Di C ++ 0x, Anda akan dapat menangkap pengecualian dengan
catch(...)
dan kemudian menyimpannya dalam contohstd::exception_ptr
penggunaanstd::current_exception()
. Anda kemudian dapat mengembalikannya nanti dari utas yang sama atau berbeda denganstd::rethrow_exception()
.Jika Anda menggunakan Microsoft Visual Studio 2005 atau yang lebih baru, maka perpustakaan thread just :: thread C ++ 0x mendukung
std::exception_ptr
. (Penafian: ini adalah produk saya).sumber
Jika Anda menggunakan C ++ 11, maka
std::future
mungkin melakukan apa yang Anda cari: secara otomatis dapat menjebak pengecualian yang membuatnya berada di atas utas pekerja, dan meneruskannya ke utas induk pada titik itu.std::future::get
adalah dipanggil. (Di balik layar, ini terjadi persis seperti jawaban @AnthonyWilliams; ini baru saja diterapkan untuk Anda.)Sisi negatifnya adalah tidak ada cara standar untuk "berhenti memedulikan" a
std::future
; bahkan penghancurnya hanya akan memblokir sampai tugas selesai.[EDIT, 2017: Perilaku penghancur-pemblokiran adalah kesalahan fungsi hanya dari masa depan semu yang dikembalikanstd::async
, yang seharusnya tidak pernah Anda gunakan. Masa depan normal tidak menghalangi penghancurnya. Tapi Anda masih tidak bisa "membatalkan" tugas jika Anda menggunakanstd::future
: tugas yang memenuhi janji akan terus berjalan di belakang layar meskipun tidak ada lagi yang mendengarkan jawabannya.] Berikut adalah contoh mainan yang mungkin menjelaskan apa yang saya berarti:Saya baru saja mencoba menulis contoh karya yang mirip menggunakan
std::thread
danstd::exception_ptr
, tetapi ada yang salah denganstd::exception_ptr
(menggunakan libc ++) jadi saya belum membuatnya benar-benar berfungsi. :([EDIT, 2017:
Saya tidak tahu apa yang saya lakukan salah di tahun 2013, tapi saya yakin itu salah saya.]
sumber
f
dan kemudianemplace_back
itu? Tidak bisakah Anda melakukannyawaitables.push_back(std::async(…));
atau saya mengabaikan sesuatu (Ini terkompilasi, pertanyaannya adalah apakah itu bisa bocor, tetapi saya tidak melihat bagaimana)?wait
? Sesuatu di sepanjang garis "segera setelah salah satu pekerjaan gagal, yang lain tidak lagi penting".async
mengembalikan masa depan daripada sesuatu-lain). Re "Juga, apakah ada": Tidak distd::future
, tapi lihat pembicaraan Sean Parent "Kode Lebih Baik: Konkurensi" atau "Masa Depan dari Awal" saya untuk berbagai cara menerapkannya jika Anda tidak keberatan menulis ulang seluruh STL sebagai permulaan. :) Kata kunci pencariannya adalah "pembatalan".Masalah Anda adalah bahwa Anda dapat menerima beberapa pengecualian, dari beberapa utas, karena masing-masing dapat gagal, mungkin karena alasan yang berbeda.
Saya berasumsi utas utama entah bagaimana menunggu utas berakhir untuk mengambil hasil, atau memeriksa kemajuan utas lain secara teratur, dan akses ke data bersama disinkronkan.
Solusi sederhana
Solusi sederhananya adalah menangkap semua pengecualian di setiap utas, merekamnya dalam variabel bersama (di utas utama).
Setelah semua utas selesai, putuskan apa yang harus dilakukan dengan pengecualian. Ini berarti bahwa semua utas lainnya melanjutkan pemrosesannya, yang mungkin, bukan yang Anda inginkan.
Solusi yang kompleks
Solusi yang lebih kompleks adalah meminta setiap utas Anda memeriksa titik-titik strategis pelaksanaannya, jika pengecualian dilemparkan dari utas lain.
Jika utas menampilkan pengecualian, itu ditangkap sebelum keluar dari utas, objek pengecualian disalin ke beberapa wadah di utas utama (seperti dalam solusi sederhana), dan beberapa variabel boolean bersama disetel ke true.
Dan ketika thread lain menguji boolean ini, ia melihat bahwa eksekusi harus dibatalkan, dan dibatalkan dengan cara yang anggun.
Jika semua utas dibatalkan, utas utama dapat menangani pengecualian sesuai kebutuhan.
sumber
Pengecualian yang dilemparkan dari utas tidak akan bisa ditangkap di utas induk. Utas memiliki konteks dan tumpukan yang berbeda, dan umumnya utas induk tidak diharuskan untuk tetap di sana dan menunggu anak-anak selesai, sehingga dapat menangkap pengecualian mereka. Tidak ada tempat dalam kode untuk tangkapan itu:
Anda perlu menangkap pengecualian di dalam setiap utas dan menafsirkan status keluar dari utas di utas utama untuk mengembalikan pengecualian apa pun yang mungkin Anda perlukan.
BTW, jika tidak ada tangkapan dalam utas itu adalah implementasi khusus jika pelepasan tumpukan akan dilakukan sama sekali, yaitu penghancur variabel otomatis Anda bahkan mungkin tidak dipanggil sebelum penghentian dipanggil. Beberapa kompiler melakukannya, tetapi itu tidak wajib.
sumber
Bisakah Anda membuat serial pengecualian di thread pekerja, mengirimkannya kembali ke thread utama, melakukan deserialisasi, dan membuangnya lagi? Saya berharap agar ini berfungsi, semua pengecualian harus berasal dari kelas yang sama (atau setidaknya satu set kelas kecil dengan pernyataan switch lagi). Juga, saya tidak yakin bahwa mereka akan dapat diserialkan, saya hanya berpikir keras.
sumber
Tidak ada cara yang baik dan umum untuk mengirimkan pengecualian dari satu utas ke utas berikutnya.
Jika, sebagaimana mestinya, semua pengecualian Anda berasal dari std :: exception, maka Anda dapat memiliki tangkapan pengecualian umum tingkat atas yang entah bagaimana akan mengirim pengecualian ke utas utama di mana ia akan dilemparkan lagi. Masalahnya adalah Anda kehilangan titik lempar pengecualian. Anda mungkin dapat menulis kode yang bergantung pada kompilator untuk mendapatkan informasi ini dan mengirimkannya.
Jika tidak semua pengecualian Anda mewarisi std :: exception, maka Anda dalam masalah dan harus menulis banyak tangkapan tingkat atas di utas Anda ... tetapi solusinya masih berlaku.
sumber
Anda perlu melakukan penangkapan umum untuk semua pengecualian di pekerja (termasuk pengecualian non-std, seperti pelanggaran akses), dan mengirim pesan dari utas pekerja (saya kira Anda memiliki semacam pesan di tempat?) Ke pengontrol utas, berisi penunjuk langsung ke pengecualian, dan luncurkan kembali ke sana dengan membuat salinan pengecualian. Kemudian pekerja dapat membebaskan objek asli dan keluar.
sumber
Lihat http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html . Dimungkinkan juga untuk menulis fungsi pembungkus dari fungsi apa pun yang Anda panggil untuk bergabung dengan utas anak, yang secara otomatis melempar ulang (menggunakan boost :: rethrow_exception) pengecualian apa pun yang dikeluarkan oleh utas anak.
sumber