Apa yang Anda cari saat men-debug deadlock?

25

Baru-baru ini saya telah mengerjakan proyek yang banyak menggunakan threading. Saya pikir saya baik-baik saja dalam mendesainnya; gunakan desain stateless sebanyak mungkin, kunci akses ke semua sumber daya yang dibutuhkan lebih dari satu utas, dll. Pengalaman saya dalam pemrograman fungsional telah sangat membantu.

Namun, ketika membaca kode utas orang lain, saya bingung. Saya sedang debugging kebuntuan sekarang, dan karena gaya dan desain pengkodean berbeda dari gaya pribadi saya, saya mengalami kesulitan melihat kondisi kebuntuan potensial.

Apa yang Anda cari saat men-debug deadlock?

Michael K.
sumber
Saya meminta ini di sini daripada SO karena saya ingin petunjuk yang lebih umum tentang debugging deadlock, bukan jawaban spesifik untuk masalah saya.
Michael K
Strategi yang dapat saya pikirkan adalah logging (seperti yang ditunjukkan beberapa orang lain), sebenarnya memeriksa grafik kebuntuan dari siapa yang sedang menunggu kunci dipegang oleh siapa (lihat stackoverflow.com/questions/3483094/… untuk beberapa pointer) dan kunci anotasi (lihat clang.llvm.org/docs/ThreadSafetyAnalysis.html ). Meskipun bukan kode Anda, Anda dapat mencoba meyakinkan penulis untuk menambahkan anotasi - mereka mungkin akan menemukan bug dan memperbaikinya (mungkin termasuk Anda) dalam proses.
Don Hatch

Jawaban:

23

Jika situasinya benar-benar jalan buntu (yaitu dua utas memegang dua kunci berbeda, tetapi setidaknya satu utas menginginkan kunci yang dimiliki oleh utas lainnya) maka Anda harus terlebih dahulu meninggalkan semua pra-konsepsi tentang bagaimana utas memesan penguncian. Asumsikan tidak ada. Anda mungkin ingin menghapus semua komentar dari kode yang Anda lihat, karena komentar tersebut dapat membuat Anda percaya sesuatu yang tidak benar. Sulit untuk cukup menekankan hal ini: tidak menganggap apa-apa.

Setelah itu, tentukan kunci apa yang ditahan sementara utas mencoba untuk mengunci sesuatu yang lain. Jika Anda bisa, pastikan utas membuka dengan urutan terbalik dari penguncian. Lebih baik lagi, pastikan bahwa utas hanya memiliki satu kunci pada satu waktu.

Dengan susah payah bekerja melalui eksekusi utas, dan periksa semua acara penguncian. Di setiap kunci, tentukan apakah utas memegang kunci lain, dan jika demikian, dalam keadaan apa utas lainnya, yang melakukan jalur eksekusi yang sama, dapat sampai ke acara penguncian yang sedang dipertimbangkan.

Mungkin saja Anda tidak akan menemukan masalah sebelum kehabisan waktu atau uang.

Bruce Ediger
sumber
4
+1 Wow, itu pesimistis ... bukankah itu kebenarannya. Ini mengingat bahwa Anda tidak dapat menemukan semua bug. Terima kasih atas sarannya!
Michael K
Bruce, karakterisasi "jalan buntu" Anda mengejutkan bagi saya. Saya pikir jalan buntu antara dua utas adalah ketika masing-masing menunggu kunci yang dipegang oleh yang lainnya. Definisi Anda tampaknya juga mencakup kasus bahwa utas, sambil memegang satu kunci, menunggu untuk mendapatkan kunci kedua yang saat ini dimiliki oleh utas yang berbeda. Itu tidak terdengar seperti jalan buntu bagi saya; Apakah itu??
Don Hatch
@ DonHatch - Saya mengucapkannya dengan buruk. Situasi yang Anda gambarkan bukanlah jalan buntu. Saya berharap untuk menyampaikan kekacauan dalam debugging situasi yang mencakup utas menahan kunci A, kemudian mencoba untuk mendapatkan kunci B, sedangkan utas yang memegang kunci B sedang mencoba mendapatkan kunci A. Mungkin. Atau mungkin situasinya jauh lebih rumit. Anda hanya perlu menjaga pikiran yang sangat terbuka tentang urutan perolehan kunci. Periksa semua asumsi. Jangan percaya apa pun.
Bruce Ediger
+1 menyarankan untuk membaca kode dengan hati-hati dan memeriksa semua operasi kunci secara terpisah. Jauh lebih mudah untuk melihat grafik yang rumit dengan hati-hati memeriksa satu node daripada mencoba dan melihat semuanya sekaligus. Berapa kali saya menemukan masalah hanya dengan menatap kode dan menjalankan skenario yang berbeda di kepala saya.
Newtopian
11
  1. Seperti yang dikatakan orang lain ... jika Anda bisa mendapatkan informasi yang berguna untuk login maka cobalah dulu karena itu adalah hal termudah untuk dilakukan.

  2. Identifikasi kunci yang terlibat. Ubah semua mutex / semaphore yang menunggu selamanya untuk menunggu waktunya ... sesuatu yang sangat panjang seperti 5 menit. Catat kesalahan saat habis. Setidaknya ini akan mengarahkan Anda ke arah salah satu kunci yang terlibat dalam masalah ini. Tergantung variabilitas waktunya Anda mungkin beruntung dan menemukan kedua kunci setelah beberapa kali berjalan. Gunakan kode / kondisi kegagalan fungsi untuk mencatat jejak tumpukan semu setelah menunggu waktunya gagal mengidentifikasi bagaimana Anda sampai di sana di tempat pertama. Ini akan membantu Anda mengidentifikasi utas yang terlibat dalam masalah ini.

  3. Hal lain yang dapat Anda coba adalah membangun perpustakaan pembungkus di sekitar layanan mutex / semaphore Anda. Lacak utas apa yang memiliki masing-masing mutex dan utas apa yang menunggu di mutex. Buat utas monitor yang memeriksa berapa lama utas yang telah diblokir. Pemicu pada durasi yang masuk akal dan buang informasi negara yang Anda lacak.

Pada titik tertentu, inspeksi kode lama biasa akan diperlukan.

Pemda
sumber
6

Langkah pertama (seperti kata Péter) adalah mencatat. Meskipun dalam pengalaman saya ini sering bermasalah. Dalam pemrosesan paralel yang berat ini seringkali tidak mungkin. Saya harus men-debug sesuatu yang mirip dengan jaringan saraf sekali, yang memproses 100rb node per detik. Kesalahan terjadi hanya setelah beberapa jam dan bahkan satu baris output memperlambat banyak hal, sehingga butuh waktu berhari-hari. Jika logging memungkinkan, kurang berkonsentrasi pada data, tetapi lebih pada aliran program, sampai Anda tahu di bagian mana itu terjadi. Hanya garis sederhana di awal setiap fungsi dan jika Anda dapat menemukan fungsi yang tepat, bagi menjadi beberapa bagian yang lebih kecil.

Pilihan lain adalah menghapus bagian dari kode dan data untuk melokalisasi bug. Mungkin bahkan menulis beberapa program kecil yang hanya mengambil beberapa kelas dan hanya menjalankan tes paling dasar (masih dalam beberapa utas tentu saja). Hapus semua yang berhubungan dengan gui, misalnya output apa pun tentang status pemrosesan aktual. (Saya menemukan antarmuka pengguna cukup sering menjadi sumber bug)

Dalam kode Anda, coba ikuti alur kontrol logis yang lengkap antara menginisialisasi kunci dan melepaskannya. Kesalahan umum adalah mengunci di awal fungsi, membuka kunci di akhir, tetapi memiliki pernyataan pengembalian bersyarat di antara keduanya. Pengecualian juga bisa mencegah pembebasan.

thorsten müller
sumber
"Pengecualian dapat mencegah pelepasan" -> Saya kasihan bahasa yang tidak memiliki variabel cakupan: /
Matthieu M.
1
@ Matthieu: Memiliki variabel scoped dan benar-benar menggunakannya mungkin dua hal yang berbeda. Dan dia meminta kemungkinan masalah secara umum, tanpa menyebutkan bahasa tertentu. Jadi ini adalah satu hal, yang dapat mempengaruhi aliran kontrol.
thorsten müller
3

Teman baik saya telah mencetak / mencatat pernyataan di tempat-tempat menarik dalam kode. Ini biasanya membantu saya memahami lebih baik apa yang sebenarnya terjadi di dalam aplikasi, tanpa mengganggu pengaturan waktu di antara berbagai utas, yang dapat mencegah mereproduksi bug.

Jika itu gagal, satu-satunya metode saya yang tersisa adalah menatap kode dan mencoba membangun model mental dari berbagai utas dan interaksi, dan mencoba memikirkan cara-cara gila yang mungkin untuk mencapai apa yang tampaknya telah terjadi :-) Tapi saya tidak menganggap diri saya seorang pemecah kebuntuan yang sangat berpengalaman. Semoga orang lain bisa memberikan ide yang lebih baik, dari mana saya bisa belajar juga :-)

Péter Török
sumber
1
Saya men-debug beberapa kunci mati seperti ini hari ini. Caranya adalah dengan membungkus pthread_mutex_lock () dengan makro yang mencetak fungsi, nomor baris, nama file, dan nama variabel mutex (dengan tokenizing) sebelum dan setelah mendapatkan kunci. Lakukan hal yang sama untuk pthread_mutex_unlock () juga. Ketika saya melihat bahwa utas saya membeku, saya hanya harus melihat dua pesan terakhir, ada dua utas yang mencoba untuk mengunci tetapi tidak pernah menyelesaikannya! Sekarang yang tersisa adalah menambahkan mekanisme untuk beralih ini saat runtime. :-)
Plumenator
3

Pertama-tama, cobalah untuk mendapatkan pembuat kode itu. Dia mungkin akan memiliki gagasan tentang apa yang telah ditulisnya. bahkan jika Anda berdua tidak dapat menentukan masalah hanya dengan berbicara, Setidaknya Anda bisa duduk dengannya untuk menentukan bagian kebuntuan, yang akan jauh lebih cepat daripada Anda memahami kode-kodenya tanpa bantuan.

Kegagalan itu, seperti kata Péter Török, Penebangan mungkin menjadi jalannya. Sejauh yang saya tahu, Debugger melakukan pekerjaan yang buruk pada lingkungan multi-threading. cobalah untuk menemukan di mana kuncinya, dapatkan sumber daya apa yang sedang menunggu, dan dalam kondisi apa kondisi balap terjadi.

Zekta Chan
sumber
tidak, penebangan adalah musuh Anda di sini - saat Anda memasukkan proses masuk yang lambat, Anda mengubah perilaku program ke titik di mana mudah untuk mendapatkan program yang berjalan dengan baik jika logging diaktifkan, tetapi kebuntuan saat penebangan dimatikan. Ini adalah jenis masalah yang sama dengan yang Anda miliki saat menjalankan program pada satu CPU daripada multicore.
gbjbaanb
@ gbjbaanb, saya pikir mengatakan itu musuh Anda terlalu keras. Mungkin akan benar untuk mengatakan itu adalah teman baik Anda, yang membuat Anda sesekali. Saya akan setuju dengan beberapa orang lain di halaman ini yang mengatakan logging adalah langkah pertama yang baik untuk dilakukan, setelah pemeriksaan kode gagal - sering (pada kenyataannya sebagian besar waktu, dalam pengalaman saya) strategi logging sederhana akan menemukan masalah dengan mudah, dan Anda selesai. Kalau tidak, berarti menggunakan metode lain, tapi saya pikir itu bukan saran yang baik untuk menghindari mencoba apa yang paling sering merupakan alat terbaik untuk pekerjaan itu hanya karena itu tidak selalu membantu.
Don Hatch
0

Pertanyaan ini menarik saya;) Pertama-tama, anggap diri Anda beruntung karena Anda dapat mereproduksi masalah secara konsisten di setiap perjalanan. Jika Anda menerima pengecualian yang sama dengan stacktrace yang sama setiap kali maka itu harus cukup mudah. Jika tidak, maka jangan terlalu mempercayai stacktrace, alih-alih pantau saja akses ke objek global dan kondisinya berubah selama eksekusi.


sumber
0

Jika Anda harus men-debug deadlock, Anda sudah dalam masalah. Sebagai aturan, gunakan kunci untuk waktu sesingkat mungkin - atau tidak sama sekali, jika mungkin. Situasi apa pun di mana Anda mengambil kunci dan kemudian pergi ke kode non-sepele harus dihindari.

Tentu saja ini tergantung pada lingkungan pemrograman Anda, tetapi Anda harus melihat hal-hal seperti antrian berurutan yang memungkinkan Anda untuk mengakses sumber daya hanya dari satu utas.

Dan kemudian ada strategi lama tetapi sempurna: Menetapkan "level" untuk setiap kunci, mulai dari level 0. Jika Anda mengambil kunci level 0 Anda tidak diizinkan kunci lainnya. Setelah mengambil kunci level 1 Anda dapat mengambil kunci level 0. Setelah mengambil kunci level 10, Anda dapat mengambil kunci di level 9 atau lebih rendah dll.

Jika Anda menemukan ini tidak mungkin dilakukan, Anda harus memperbaiki kode Anda karena Anda akan mengalami kebuntuan.

gnasher729
sumber