Mengapa saya tidak bisa memeriksa apakah mutex terkunci?

28

C ++ 14 tampaknya telah menghilangkan mekanisme untuk memeriksa apakah suatu std::mutexterkunci atau tidak. Lihat pertanyaan SO ini:

/programming/21892934/how-to-assert-if-a-stdmutex-is-locked

Ada beberapa cara untuk mengatasi hal ini, misalnya dengan menggunakan;

std::mutex::try_lock()
std::unique_lock::owns_lock()

Tapi tak satu pun dari ini adalah solusi yang memuaskan.

try_lock()diizinkan untuk mengembalikan negatif palsu dan memiliki perilaku tidak terdefinisi jika utas saat ini telah mengunci mutex. Ini juga memiliki efek samping. owns_lock()membutuhkan konstruksi unique_lockdi atas yang asli std::mutex.

Jelas saya bisa menjalankan sendiri, tetapi saya lebih suka memahami motivasi untuk antarmuka saat ini.

Kemampuan untuk memeriksa status mutex (misalnya std::mutex::is_locked()) tidak tampak seperti permintaan esoterik bagi saya, jadi saya curiga Komite Standar sengaja menghapus fitur ini daripada menjadi pengawasan.

Mengapa?

Sunting: Oke jadi mungkin ini kasus penggunaan tidak biasa seperti yang saya harapkan, jadi saya akan menggambarkan skenario khusus saya. Saya memiliki algoritma pembelajaran mesin yang didistribusikan pada banyak utas. Setiap utas beroperasi secara tidak sinkron, dan kembali ke kumpulan induk setelah menyelesaikan masalah optimisasi.

Ini kemudian mengunci mutex master. Utas kemudian harus memilih induk baru untuk dimutasi dari keturunan, tetapi hanya dapat memilih dari induk yang saat ini tidak memiliki keturunan yang sedang dioptimalkan oleh utas lainnya. Karena itu saya perlu melakukan pencarian untuk menemukan orang tua yang saat ini tidak dikunci oleh utas lainnya. Tidak ada risiko status mutex berubah selama pencarian, karena master thread mutex dikunci. Jelas ada solusi lain (saya saat ini menggunakan bendera boolean) tapi saya pikir mutex menawarkan solusi logis untuk masalah ini, karena ada untuk keperluan sinkronisasi antar-thread.

bergalah
sumber
42
Anda tidak dapat benar-benar memeriksa apakah mutex terkunci, karena satu nanodetik setelah pengecekan dapat dibuka atau dikunci. Jadi jika Anda menulis "jika (mutex_is_locked ()) ..." maka mutex_is_locked dapat mengembalikan hasil yang benar, tetapi pada saat "jika" dijalankan, itu salah.
gnasher729
1
Ini ^. Informasi berguna apa yang Anda harap dapatkan is_locked?
berguna
3
Ini terasa seperti masalah XY. Mengapa Anda mencoba mencegah penggunaan kembali orang tua hanya ketika seorang anak dihasilkan? Apakah Anda memiliki persyaratan bahwa setiap orang tua hanya boleh memiliki satu anak saja? Kunci Anda tidak akan mencegah hal itu. Tidakkah Anda memiliki generasi yang jelas? Jika tidak, apakah Anda sadar bahwa individu yang dapat dioptimalkan lebih cepat memiliki kebugaran yang lebih tinggi, karena mereka dapat dipilih lebih sering / lebih awal? Jika Anda menggunakan generasi, mengapa Anda tidak memilih semua orang tua di depan, lalu biarkan utas mengambil orang tua dari antrian? Apakah menghasilkan keturunan sangat mahal sehingga Anda perlu banyak utas?
amon
10
@ quant - Saya tidak melihat mengapa mutex objek orangtua Anda dalam aplikasi contoh Anda harus mutex sama sekali: jika Anda memiliki master mutex yang dikunci setiap kali mereka ditetapkan, Anda bisa menggunakan variabel boolean untuk menunjukkan statusnya.
Periata Breatta
4
Saya tidak setuju dengan kalimat terakhir dari pertanyaan itu. Nilai boolean sederhana jauh lebih bersih daripada mutex di sini. Jadikan itu bool atom jika Anda tidak ingin mengunci master mutex untuk "mengembalikan" orangtua.
Sebastian Redl

Jawaban:

53

Saya dapat melihat setidaknya dua masalah parah dengan operasi yang disarankan.

Yang pertama sudah disebutkan dalam komentar oleh @ gnasher729 :

Anda tidak dapat benar-benar memeriksa apakah mutex terkunci, karena satu nanodetik setelah pengecekan dapat dibuka atau dikunci. Jadi jika Anda menulis if (mutex_is_locked ()) …maka mutex_is_lockeddapat mengembalikan hasil yang benar, tetapi pada saat ifdieksekusi, itu salah.

Satu-satunya cara untuk memastikan bahwa properti "saat ini dikunci" dari sebuah mutex tidak berubah adalah dengan, well, kunci sendiri.

Masalah kedua yang saya lihat adalah bahwa kecuali Anda mengunci mutex, utas Anda tidak disinkronkan dengan utas yang sebelumnya mengunci mutex. Oleh karena itu, bahkan tidak didefinisikan dengan baik untuk berbicara tentang "sebelum" dan "setelah" dan apakah mutex terkunci atau tidak adalah semacam bertanya apakah kucing Schrödiger saat ini hidup tanpa berusaha membuka kotak.

Jika saya mengerti benar, maka kedua masalah akan diperdebatkan dalam kasus khusus Anda berkat master mutex yang dikunci. Tetapi ini sepertinya bukan kasus yang umum bagi saya, jadi saya pikir panitia melakukan hal yang benar dengan tidak menambahkan fungsi yang mungkin berguna dalam skenario yang sangat khusus dan menyebabkan kerusakan pada yang lain. (Dengan semangat: "Jadikan antarmuka mudah digunakan dengan benar dan salah digunakan.")

Dan jika boleh saya katakan, saya pikir setup yang Anda miliki saat ini bukan yang paling elegan dan bisa di refactored untuk menghindari masalah sama sekali. Misalnya, alih-alih master thread memeriksa semua calon orang tua untuk satu yang saat ini tidak dikunci, mengapa tidak memelihara antrian orang tua yang siap? Jika utas ingin mengoptimalkan yang lain, utas yang berikutnya akan keluar dari antrian dan segera setelah memiliki orangtua baru, ia menambahkannya ke antrian. Dengan begitu, Anda bahkan tidak perlu utas utama sebagai koordinator.

5gon12eder
sumber
Terima kasih, ini jawaban yang bagus. Alasan saya tidak ingin mempertahankan antrian orang tua yang siap adalah bahwa saya perlu menjaga urutan di mana orang tua diciptakan (karena ini menentukan umur mereka). Ini mudah dilakukan dengan antrian LIFO. Jika saya mulai menarik barang masuk dan keluar saya harus mempertahankan mekanisme pemesanan terpisah yang akan mempersulit hal-hal, maka pendekatan saat ini.
quant
14
@quant: Jika Anda memiliki dua tujuan untuk mengantri orang tua, Anda dapat melakukannya dengan dua antrian ....
@quant: Anda menghapus item (paling banyak) satu kali, tetapi mungkin melakukan pemrosesan pada setiap beberapa kali, jadi Anda mengoptimalkan kasing langka dengan mengorbankan kasing umum. Ini jarang diinginkan.
Jerry Coffin
2
Tapi itu adalah wajar untuk bertanya apakah thread saat telah mengunci mutex.
Penebusan Terbatas
@LimitedAtonement Tidak juga. Untuk melakukan mutex ini harus menyimpan informasi tambahan (utas id), membuatnya lebih lambat. Mutex rekursif sudah melakukan ini, Anda harus melakukannya sebagai gantinya.
StaceyGirl
9

Tampaknya Anda menggunakan mutex sekunder untuk tidak mengunci akses ke masalah optimisasi, tetapi untuk menentukan apakah masalah optimisasi sedang dioptimalkan saat ini atau tidak.

Itu sama sekali tidak perlu. Saya memiliki daftar masalah yang perlu dioptimalkan, daftar masalah yang dioptimalkan saat ini, dan daftar masalah yang telah dioptimalkan. (Jangan mengambil "daftar" secara harfiah, anggap itu berarti "struktur data yang sesuai).

Operasi menambahkan masalah baru ke daftar masalah yang tidak dioptimalkan, atau memindahkan masalah dari satu daftar ke daftar berikutnya, akan dilakukan di bawah perlindungan satu "master" mutex.

gnasher729
sumber
1
Anda tidak berpikir bahwa objek bertipe std::mutexcocok untuk struktur data seperti itu?
quant
2
@ quant - tidak. std::mutexbergantung pada implementasi sistem mutasi yang ditentukan oleh sistem operasi yang mungkin membutuhkan sumber daya (mis. pegangan) yang terbatas dan lambat untuk dialokasikan dan / atau beroperasi. Menggunakan satu mutex untuk mengunci akses ke struktur data internal kemungkinan akan jauh lebih efisien, dan mungkin lebih terukur juga.
Periata Breatta
1
Juga pertimbangkan Variabel Kondisi. Mereka dapat membuat banyak struktur data seperti ini sangat mudah.
Cort Ammon - Reinstate Monica
2

Seperti yang dikatakan orang lain, tidak ada kasus penggunaan di mana is_lockedpada mutex ada manfaatnya, itu sebabnya fungsinya tidak ada.

Kasus Anda mengalami masalah dengan ini sangat umum, itu dasarnya apa benang pekerja lakukan, yang merupakan salah satu, jika tidak yang pelaksanaan umum yang paling benang.

Anda memiliki rak dengan 10 kotak di atasnya. Anda memiliki 4 pekerja yang bekerja dengan kotak-kotak ini. Bagaimana Anda memastikan keempat pekerja mengerjakan kotak yang berbeda? Pekerja pertama mengambil sebuah kotak dari rak sebelum mereka mulai mengerjakannya. Pekerja kedua melihat 9 kotak di rak.

Tidak ada mutex untuk mengunci kotak, jadi melihat keadaan mutex imajiner pada kotak tidak diperlukan, dan menyalahgunakan mutex sebagai boolean adalah salah. Mutex mengunci rak.

Peter
sumber
1

Selain dua alasan yang diberikan dalam jawaban 5gon12eder di atas, saya ingin menambahkan bahwa itu tidak perlu atau tidak diinginkan.

Jika Anda sudah memegang mutex, maka Anda sebaiknya tahu bahwa Anda memegangnya! Anda tidak perlu bertanya. Sama seperti memiliki blok memori atau sumber daya lainnya, Anda harus tahu persis apakah Anda memilikinya atau tidak, dan kapan tepat untuk melepaskan / menghapus sumber daya tersebut.
Jika bukan itu masalahnya, program Anda dirancang dengan buruk, dan Anda menuju masalah.

Jika Anda perlu mengakses sumber daya bersama yang dilindungi oleh mutex, dan Anda belum memegang mutex, maka Anda perlu memperoleh mutex. Tidak ada pilihan lain, jika tidak logika program Anda tidak benar.
Anda mungkin menemukan pemblokiran diterima atau tidak dapat diterima, dalam kedua kasus lock()atau try_lock()akan memberikan perilaku yang Anda inginkan. Yang perlu Anda ketahui, secara positif, dan tanpa keraguan, adalah apakah Anda berhasil memperoleh mutex (nilai balik dari try_lockmemberi tahu Anda). Tidak penting apakah orang lain memegangnya atau apakah Anda mendapatkan kegagalan palsu.

Dalam setiap kasus lainnya, terus terang, itu bukan urusan Anda. Anda tidak perlu tahu, dan Anda seharusnya tidak tahu, atau membuat asumsi (untuk masalah ketepatan waktu dan sinkronisasi yang disebutkan dalam pertanyaan lain).

Damon
sumber
1
Bagaimana jika saya ingin melakukan operasi peringkat pada sumber daya yang saat ini tersedia untuk mengunci?
quant
Tetapi apakah itu sesuatu yang realistis terjadi? Saya anggap itu agak tidak biasa. Saya akan mengatakan salah satu sumber daya sudah memiliki semacam peringkat intrinsik, maka Anda perlu melakukan (memperoleh kunci untuk) yang lebih penting terlebih dahulu. Contoh: Harus memperbarui simulasi fisika sebelum rendering. Atau, peringkatnya kurang lebih disengaja, maka Anda dapat juga try_locksumber daya pertama, dan jika yang satu gagal, coba yang kedua. Contoh: Tiga koneksi yang terus-menerus dan dikumpulkan ke server database, dan Anda perlu menggunakan satu untuk mengirim perintah.
Damon
4
@quant - "operasi peringkat pada sumber daya yang saat ini tersedia untuk mengunci" - secara umum, melakukan hal semacam ini adalah cara yang sangat mudah dan cepat untuk menulis kode yang mengalami kebuntuan dengan cara yang Anda berjuang untuk mencari tahu. Membuat akuisisi kunci dan melepaskan deterministik , dalam hampir semua kasus, adalah kebijakan terbaik. Mencari kunci berdasarkan kriteria yang dapat berubah meminta masalah.
Periata Breatta
@PeriataBreatta Program saya sengaja tidak ditentukan. Saya melihat sekarang bahwa atribut ini tidak umum, jadi saya bisa memahami kelalaian fitur seperti is_locked()itu yang mungkin memfasilitasi perilaku tersebut.
quant
Pemeringkatan dan penguncian semu adalah masalah yang sepenuhnya terpisah. Jika Anda ingin entah bagaimana mengurutkan atau menyusun ulang antrian dengan kunci, menguncinya, mengurutkannya, lalu membukanya. Jika Anda membutuhkan is_locked, maka ada solusi yang jauh lebih baik untuk masalah Anda daripada yang ada dalam pikiran Anda.
Peter
1

Anda mungkin ingin menggunakan atomic_flag dengan urutan memori default. Itu tidak memiliki ras data dan tidak pernah melempar pengecualian seperti mutex tidak dengan beberapa panggilan pembuka kunci (dan dibatalkan secara tidak terkendali, saya dapat menambahkan ...). Atau, ada atom (mis. Atom [bool] atau atom [int] (dengan tanda kurung segitiga, bukan [])), yang memiliki fungsi bagus seperti memuat dan compare_exchange_strong.

Andrew
sumber
1

Saya ingin menambahkan use-case untuk ini: Ini akan memungkinkan fungsi internal untuk memastikan sebagai prasyarat / pernyataan bahwa penelepon memang memegang kunci.

Untuk kelas dengan beberapa fungsi internal seperti itu, dan mungkin banyak fungsi publik memanggil mereka, itu bisa memastikan, bahwa seseorang menambahkan fungsi publik lain memanggil internal itu memang memperoleh kunci.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
B3ret
sumber