Saya punya daftar yang saya ingin utas berbeda untuk mengambil elemen. Untuk menghindari mengunci mutex yang menjaga daftar ketika kosong, saya periksa empty()
sebelum mengunci.
Tidak apa-apa jika panggilan list::empty()
tidak 100% benar. Saya hanya ingin menghindari menabrak atau mengganggu panggilan list::push()
dan list::pop()
panggilan bersamaan .
Apakah saya aman untuk mengasumsikan VC ++ dan Gnu GCC hanya akan terkadang empty()
salah dan tidak ada yang lebih buruk?
if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
mutex.lock();
if(list.empty() == false){ // check again while locked to be certain
element = list.back();
list.pop_back();
}
mutex.unlock();
}
c++
multithreading
optimization
race-condition
stdlist
Anne Quinn
sumber
sumber
std::list::size
telah menjamin kompleksitas waktu yang konstan, yang pada dasarnya berarti bahwa ukuran (jumlah node) perlu disimpan dalam variabel yang terpisah; sebut saja itusize_
.std::list::empty
maka kemungkinan mengembalikan sesuatu sebagaisize_ == 0
, dan membaca dan menulis bersamaansize_
akan menyebabkan perlombaan data, oleh karena itu, UB.Jawaban:
Tidak, ini tidak baik. Jika Anda memeriksa apakah daftar kosong di luar beberapa mekanisme sinkronisasi (mengunci mutex) maka Anda memiliki ras data. Memiliki ras data berarti Anda memiliki perilaku yang tidak terdefinisi. Memiliki perilaku yang tidak terdefinisi berarti kita tidak dapat lagi alasan tentang program dan output yang Anda dapatkan adalah "benar".
Jika Anda menghargai kewarasan Anda, Anda akan mendapatkan hit kinerja dan mengunci mutex sebelum memeriksa. Yang mengatakan, daftar itu mungkin bahkan bukan wadah yang tepat untuk Anda. Jika Anda dapat memberi tahu kami apa yang Anda lakukan dengannya, kami mungkin dapat menyarankan wadah yang lebih baik.
sumber
list::empty()
adalah tindakan baca yang tidak ada hubungannya denganrace-condition
read-write
atauwrite-write
. Jika Anda tidak percaya saya, memberikan bagian standar pada ras data yang dibacaAda membaca dan menulis (kemungkinan besar untuk
size
anggotastd::list
, jika kita berasumsi bahwa itu dinamai seperti itu) yang tidak disinkronkan dalam reagard satu sama lain . Bayangkan bahwa satu utas memanggilempty()
(di bagian luar Andaif()
) sedangkan utas lainnya masuk ke dalamif()
dan mengeksekusipop_back()
. Anda kemudian membaca variabel yang, mungkin, sedang dimodifikasi. Ini adalah perilaku yang tidak terdefinisi.sumber
Sebagai contoh bagaimana segala sesuatunya salah:
Kompiler yang cukup pintar dapat melihat bahwa
mutex.lock()
tidak mungkin mengubah nilailist.empty()
kembali dan dengan demikian melewatkanif
pemeriksaan dalam sepenuhnya, akhirnya mengarahpop_back
pada daftar yang unsur terakhirnya dihapus setelah yang pertamaif
.Kenapa bisa begitu? Tidak ada sinkronisasi di
list.empty()
, jadi jika itu diubah secara bersamaan yang akan membentuk perlombaan data. Standar mengatakan bahwa program tidak boleh memiliki data balapan, sehingga kompiler akan menerima begitu saja (jika tidak, ia dapat melakukan hampir tidak ada optimasi sama sekali). Oleh karena itu dapat mengasumsikan perspektif single-threaded pada yang tidak disinkronkanlist.empty()
dan menyimpulkan bahwa itu harus tetap konstan.Ini hanya satu dari beberapa optimasi (atau perilaku perangkat keras) yang dapat merusak kode Anda.
sumber
a.load()+a.load()
...a.load()*2
itu jelas? Bahkana.load(rel)+b.load(rel)-a.load(rel)
tidak dioptimalkan. Tidak ada yang. Mengapa Anda berharap kunci (yang pada dasarnya sebagian besar memiliki konsistensi seq) lebih dioptimalkan?a.load() + a.load()
untuk2 * a.load()
. Jangan ragu untuk bertanya tentang hal ini jika Anda ingin tahu lebih banyak.