POSIX memungkinkan mutex bersifat rekursif. Itu berarti utas yang sama dapat mengunci mutex yang sama dua kali dan tidak akan menemui jalan buntu. Tentu saja itu juga perlu membuka kuncinya dua kali, jika tidak, tidak ada utas lain yang dapat memperoleh mutex. Tidak semua sistem yang mendukung pthreads juga mendukung mutex rekursif, tetapi jika mereka ingin menjadi POSIX, mereka harus melakukannya .
API lain (lebih banyak API tingkat tinggi) juga biasanya menawarkan mutex, sering disebut Kunci. Beberapa sistem / bahasa (misalnya Cocoa Objective-C) menawarkan mutex keduanya, rekursif dan non rekursif. Beberapa bahasa juga hanya menawarkan satu atau yang lain. Misal dalam Java mutex selalu bersifat rekursif (utas yang sama mungkin dua kali "menyinkronkan" pada objek yang sama). Bergantung pada fungsionalitas utas lainnya yang mereka tawarkan, tidak memiliki mutex rekursif mungkin tidak ada masalah, karena mereka dapat dengan mudah ditulis sendiri (saya sudah menerapkan sendiri mutex rekursif sendiri berdasarkan operasi mutex / kondisi yang lebih sederhana).
Apa yang tidak saya mengerti: Untuk apa mutex non-rekursif itu bagus? Mengapa saya ingin kebuntuan utas jika mengunci mutex yang sama dua kali? Bahkan bahasa tingkat tinggi yang dapat menghindari itu (misalnya menguji apakah ini akan menemui jalan buntu dan melemparkan pengecualian jika itu benar) biasanya tidak melakukan itu. Mereka akan membiarkan kebuntuan utas sebagai gantinya.
Apakah ini hanya untuk kasus-kasus, di mana saya secara tidak sengaja menguncinya dua kali dan hanya membukanya sekali dan dalam kasus mutex rekursif, akan lebih sulit untuk menemukan masalahnya, jadi alih-alih saya langsung menemui jalan buntu untuk melihat di mana kunci yang salah muncul? Tetapi tidak bisakah saya melakukan hal yang sama dengan mengembalikan penghitung kunci saat membuka kunci dan dalam situasi, di mana saya yakin saya melepaskan kunci terakhir dan penghitungnya tidak nol, saya bisa melempar pengecualian atau mencatat masalahnya? Atau apakah ada kasus penggunaan lain yang lebih berguna dari mutex non rekursif yang gagal saya lihat? Atau mungkin hanya kinerja, karena mutex non-rekursif bisa sedikit lebih cepat daripada yang rekursif? Namun, saya menguji ini dan perbedaannya tidak terlalu besar.
Jawabannya bukan efisiensi. Mutex non-reentrant mengarah pada kode yang lebih baik.
Contoh: A :: foo () memperoleh kunci. Itu kemudian memanggil B :: bar (). Ini berfungsi dengan baik ketika Anda menulisnya. Tetapi beberapa saat kemudian seseorang mengubah B :: bar () untuk memanggil A :: baz (), yang juga memperoleh kunci.
Nah, jika Anda tidak memiliki mutex rekursif, ini deadlock. Jika Anda memilikinya, itu berjalan, tetapi mungkin rusak. A :: foo () mungkin telah meninggalkan objek dalam keadaan tidak konsisten sebelum memanggil bar (), dengan asumsi bahwa baz () tidak dapat dijalankan karena ia juga memperoleh mutex. Tapi mungkin tidak boleh berjalan! Orang yang menulis A :: foo () berasumsi bahwa tidak ada yang bisa memanggil A :: baz () pada saat yang sama - itulah alasan mengapa kedua metode tersebut memperoleh kunci.
Model mental yang tepat untuk menggunakan mutex: Mutex melindungi invarian. Ketika mutex dipegang, invarian dapat berubah, tetapi sebelum melepaskan mutex, invarian dibuat kembali. Kunci reentran berbahaya karena kedua kali Anda mendapatkan kunci, Anda tidak dapat memastikan invarian benar.
Jika Anda senang dengan kunci reentrant, itu hanya karena Anda belum harus men-debug masalah seperti ini sebelumnya. Java memiliki kunci non-reentrant hari ini di java.util.concurrent.locks, omong-omong.
sumber
Semaphore
,.A::foo()
mungkin masih meninggalkan objek dalam keadaan tidak konsisten sebelum memanggilA::bar()
. Apa hubungan mutex, rekursif atau tidak, yang ada hubungannya dengan kasus ini?Seperti yang ditulis oleh Dave Butenhof sendiri :
"Yang terbesar dari semua masalah besar dengan mutasi rekursif adalah mereka mendorong Anda untuk sepenuhnya kehilangan jejak skema penguncian dan ruang lingkup Anda. Ini mematikan. Jahat. Ini adalah" pemakan utas ". Anda memegang kunci untuk waktu sesingkat mungkin. Jika Anda memanggil sesuatu dengan kunci dipegang hanya karena Anda tidak tahu itu dipegang, atau karena Anda tidak tahu apakah callee membutuhkan mutex, maka Anda memegangnya terlalu lama. membidik senapan pada aplikasi Anda dan menarik pelatuknya. Anda mungkin mulai menggunakan utas untuk mendapatkan konkurensi; tetapi Anda baru saja MENCEGAH konkurensi. "
sumber
...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
Mengapa Anda yakin bahwa ini adalah model mental yang benar untuk menggunakan mutex? Saya pikir model yang tepat adalah melindungi data tetapi tidak invarian.
Masalah melindungi invarian hadir bahkan dalam aplikasi single-threaded dan tidak memiliki kesamaan dengan multi-threading dan mutex.
Selanjutnya, jika Anda perlu melindungi invarian, Anda masih dapat menggunakan semaphore biner yang tidak pernah bersifat rekursif.
sumber
Salah satu alasan utama bahwa mutex rekursif berguna adalah dalam hal mengakses metode beberapa kali dengan utas yang sama. Sebagai contoh, katakanlah jika mutex lock melindungi bank A / c untuk ditarik, maka jika ada biaya yang juga terkait dengan penarikan itu, maka mutex yang sama harus digunakan.
sumber
Satu-satunya kasus penggunaan yang baik untuk mutasi rekursi adalah ketika suatu objek berisi beberapa metode. Ketika salah satu metode memodifikasi konten objek, dan karenanya harus mengunci objek sebelum keadaan konsisten lagi.
Jika metode menggunakan metode lain (yaitu: addNewArray () memanggil addNewPoint (), dan menyelesaikan dengan recheckBounds ()), tetapi salah satu dari fungsi itu sendiri perlu mengunci mutex, maka mutex rekursif adalah win-win.
Untuk kasus lain (menyelesaikan pengkodean yang buruk, menggunakannya bahkan pada objek yang berbeda) jelas salah!
sumber
Mereka benar-benar baik ketika Anda harus memastikan mutex dibuka sebelum melakukan sesuatu. Ini karena
pthread_mutex_unlock
dapat menjamin bahwa mutex tidak terkunci hanya jika tidak rekursif.Jika
g_mutex
non-rekursif, kode di atas dijamin untuk memanggilbar()
dengan mutex yang tidak dikunci .Dengan demikian menghilangkan kemungkinan kebuntuan jika
bar()
terjadi fungsi eksternal yang tidak diketahui yang mungkin melakukan sesuatu yang dapat mengakibatkan utas lain mencoba untuk mendapatkan mutex yang sama. Skenario semacam itu tidak jarang dalam aplikasi yang dibangun di atas kumpulan thread, dan dalam aplikasi terdistribusi, di mana panggilan antarproses dapat menelurkan utas baru tanpa disadari bahkan oleh programmer klien. Dalam semua skenario seperti itu, terbaik untuk menjalankan fungsi eksternal tersebut hanya setelah kunci dilepaskan.Jika
g_mutex
rekursif, tidak akan ada cara untuk memastikan itu dibuka sebelum melakukan panggilan.sumber