Katakanlah sebuah kelas memiliki public int counter
bidang yang diakses oleh banyak utas. Ini int
hanya bertambah atau berkurang.
Untuk menambah bidang ini, pendekatan mana yang harus digunakan, dan mengapa?
lock(this.locker) this.counter++;
,Interlocked.Increment(ref this.counter);
,- Ubah pengubah akses
counter
menjadipublic volatile
.
Sekarang saya telah menemukan volatile
, saya telah menghapus banyak lock
pernyataan dan penggunaan Interlocked
. Tetapi adakah alasan untuk tidak melakukan ini?
Jawaban:
Terburuk (tidak akan bekerja)
Seperti yang orang lain katakan, ini sendiri sebenarnya tidak aman sama sekali. Intinya
volatile
adalah bahwa banyak utas yang berjalan pada banyak CPU dapat dan akan menyimpan data dan memesan kembali instruksi.Jika tidak
volatile
, dan CPU A menambah nilai, maka CPU B mungkin tidak benar-benar melihat nilai tambah itu sampai beberapa waktu kemudian, yang dapat menyebabkan masalah.Jika ya
volatile
, ini hanya memastikan dua CPU melihat data yang sama secara bersamaan. Itu tidak menghentikan mereka sama sekali dari interleaving membaca dan menulis operasi yang merupakan masalah yang Anda coba hindari.Kedua terbaik:
Ini aman untuk dilakukan (asalkan Anda ingat ke
lock
tempat lain yang Anda aksesthis.counter
). Ini mencegah utas lainnya dari mengeksekusi kode lain yang dijaga olehlocker
. Menggunakan kunci juga, mencegah masalah penyusunan ulang multi-CPU seperti di atas, yang sangat bagus.Masalahnya adalah, pengunciannya lambat, dan jika Anda menggunakan kembali
locker
di beberapa tempat lain yang tidak benar-benar terkait maka Anda dapat memblokir utas lainnya tanpa alasan.Terbaik
Ini aman, karena secara efektif membaca, menambah, dan menulis dalam 'satu klik' yang tidak dapat diganggu. Karena itu, ini tidak akan memengaruhi kode lain, dan Anda tidak perlu ingat untuk mengunci di tempat lain juga. Ini juga sangat cepat (seperti yang dikatakan MSDN, pada CPU modern, ini seringkali secara harfiah merupakan instruksi CPU tunggal).
Namun saya tidak sepenuhnya yakin apakah itu bisa mengatasi hal-hal penataan ulang CPU lain, atau jika Anda juga perlu menggabungkan volatile dengan kenaikan.Catatan Interlocked:
Catatan kaki: Apa volatile sebenarnya baik untuk.
Karena
volatile
tidak mencegah masalah multithreading semacam ini, untuk apa? Contoh yang baik adalah mengatakan Anda memiliki dua utas, satu yang selalu menulis ke variabel (katakanlahqueueLength
), dan satu yang selalu membaca dari variabel yang sama.Jika
queueLength
tidak mudah menguap, utas A dapat menulis lima kali, tetapi utas B dapat melihat bahwa penulisan tersebut tertunda (atau bahkan berpotensi dalam urutan yang salah).Sebuah solusi adalah mengunci, tetapi Anda juga bisa menggunakan volatile dalam situasi ini. Ini akan memastikan bahwa utas B akan selalu melihat hal terbaru yang ditulis oleh utas A. Namun perlu dicatat bahwa logika ini hanya berfungsi jika Anda memiliki penulis yang tidak pernah membaca, dan pembaca yang tidak pernah menulis, dan jika hal yang Anda tulis adalah nilai atom. Segera setelah Anda melakukan satu baca-modifikasi-tulis, Anda harus pergi ke operasi Saling Bertautan atau menggunakan Kunci.
sumber
EDIT: Seperti yang tercantum dalam komentar, hari ini saya senang menggunakan
Interlocked
untuk kasus-kasus variabel tunggal di mana itu jelas oke Ketika semakin rumit, saya masih akan kembali ke penguncian ...Menggunakan
volatile
tidak akan membantu ketika Anda perlu menambah - karena membaca dan menulis adalah instruksi terpisah. Utas lain dapat mengubah nilai setelah Anda membaca tetapi sebelum Anda menulis kembali.Secara pribadi saya hampir selalu hanya mengunci - lebih mudah untuk mendapatkan dengan cara yang jelas benar daripada volatilitas atau Interlocked. Sejauh yang saya ketahui, multi-threading bebas kunci adalah untuk para ahli threading sungguhan, di mana saya bukan salah satunya. Jika Joe Duffy dan timnya membangun perpustakaan yang bagus yang akan memparalelkan berbagai hal tanpa mengunci sebanyak yang saya bangun, itu luar biasa, dan saya akan menggunakannya dalam sekejap - tetapi ketika saya melakukan threading sendiri, saya mencoba untuk tetap sederhana.
sumber
"
volatile
" tidak digantiInterlocked.Increment
! Itu hanya memastikan bahwa variabel tidak di-cache, tetapi digunakan secara langsung.Menambah variabel sebenarnya membutuhkan tiga operasi:
Interlocked.Increment
melakukan ketiga bagian sebagai operasi atom tunggal.sumber
volatile
tidak tidak pastikan variabel tidak di-cache. Itu hanya menempatkan pembatasan pada bagaimana itu bisa di-cache. Sebagai contoh, masih bisa di-cache dalam hal-hal cache L2 CPU karena mereka dibuat koheren dalam perangkat keras. Itu masih bisa diutamakan. Menulis masih dapat diposting ke cache, dan sebagainya. (Yang menurut saya adalah tujuan Zach.)Penguncian atau penambahan yang saling terkait adalah yang Anda cari.
Volatile jelas bukan yang Anda cari - ia hanya memberitahu kompiler untuk memperlakukan variabel sebagai selalu berubah bahkan jika jalur kode saat ini memungkinkan kompiler untuk mengoptimalkan pembacaan dari memori sebaliknya.
misalnya
jika m_Var disetel ke false di utas lain tetapi tidak dinyatakan sebagai volatil, kompiler bebas untuk membuatnya menjadi infinite loop (tetapi tidak berarti selalu akan) dengan membuatnya memeriksa terhadap register CPU (mis. EAX karena itu dulu apa m_Var diambil dari awal) alih-alih mengeluarkan bacaan lain ke lokasi memori m_Var (ini mungkin di-cache - kita tidak tahu dan tidak peduli dan itulah titik koherensi cache x86 / x64). Semua posting sebelumnya oleh orang lain yang menyebutkan perintah reordering hanya menunjukkan mereka tidak mengerti arsitektur x86 / x64. Volatile tidakmasalah baca / tulis hambatan seperti yang tersirat oleh posting sebelumnya mengatakan 'itu mencegah pemesanan ulang'. Faktanya, terima kasih lagi ke protokol MESI, kami dijamin hasil yang kami baca selalu sama di semua CPU terlepas dari apakah hasil sebenarnya telah dipensiunkan ke memori fisik atau hanya tinggal di cache CPU lokal. Saya tidak akan masuk terlalu jauh ke rincian ini tetapi yakinlah bahwa jika ini salah, Intel / AMD kemungkinan akan mengeluarkan penarikan prosesor! Ini juga berarti bahwa kita tidak harus peduli dengan eksekusi yang tidak sesuai, dll. Hasil selalu dijamin untuk pensiun - jika tidak, kita diisi!
Dengan Interlocked Increment, prosesor harus keluar, mengambil nilai dari alamat yang diberikan, lalu menambahkan dan menulisnya kembali - semuanya sambil memiliki kepemilikan eksklusif atas seluruh baris cache (kunci xadd) untuk memastikan tidak ada prosesor lain yang dapat memodifikasi nilainya.
Dengan volatile, Anda masih akan mendapatkan hanya 1 instruksi (dengan asumsi JIT efisien sebagaimana mestinya) - inc dword ptr [m_Var]. Namun, prosesor (cpuA) tidak meminta kepemilikan eksklusif dari garis cache saat melakukan semua yang dilakukan dengan versi yang saling terkait. Seperti yang dapat Anda bayangkan, ini berarti prosesor lain dapat menulis nilai yang diperbarui kembali ke m_Var setelah dibaca oleh cpuA. Jadi alih-alih sekarang telah meningkatkan nilainya dua kali, Anda berakhir hanya dengan sekali.
Semoga ini bisa menyelesaikan masalah.
Untuk info lebih lanjut, lihat 'Memahami Dampak Teknik Kunci Rendah di Aplikasi Multithreaded' - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx
ps Apa yang mendorong balasan yang sangat terlambat ini? Semua balasan sangat salah (terutama yang ditandai sebagai jawaban) dalam penjelasan mereka, saya hanya perlu menjelaskannya kepada orang lain yang membaca ini. mengangkat bahu
pps Saya berasumsi bahwa targetnya adalah x86 / x64 dan bukan IA64 (ia memiliki model memori yang berbeda). Perhatikan bahwa spesifikasi ECMA Microsoft kacau karena menentukan model memori terlemah dan bukan yang terkuat (selalu lebih baik untuk menentukan terhadap model memori terkuat sehingga konsisten di seluruh platform - jika tidak kode yang akan berjalan 24-7 pada x86 / x64 tidak dapat berjalan sama sekali pada IA64 meskipun Intel telah menerapkan model memori yang sama kuat untuk IA64) - Microsoft mengakui hal ini sendiri - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx .
sumber
Fungsi yang saling bertautan tidak mengunci. Mereka atom, artinya mereka dapat menyelesaikan tanpa kemungkinan perubahan konteks selama kenaikan. Jadi tidak ada kemungkinan kebuntuan atau menunggu.
Saya akan mengatakan bahwa Anda harus selalu lebih suka kunci dan kenaikan.
Volatile berguna jika Anda perlu menulis di satu utas untuk dibaca di yang lain, dan jika Anda ingin optimizer tidak menyusun ulang operasi pada variabel (karena hal-hal terjadi di utas lain yang tidak diketahui oleh optimizer). Ini adalah pilihan ortogonal untuk bagaimana Anda meningkatkan.
Ini adalah artikel yang sangat bagus jika Anda ingin membaca lebih lanjut tentang kode bebas kunci, dan cara yang tepat untuk mendekati menulisnya
http://www.ddj.com/hpc-high-performance-computing/210604448
sumber
kunci (...) berfungsi, tetapi dapat memblokir utas, dan dapat menyebabkan kebuntuan jika kode lain menggunakan kunci yang sama dengan cara yang tidak kompatibel.
Saling bertautan. * Adalah cara yang tepat untuk melakukannya ... apalagi overhead karena CPU modern mendukung ini sebagai primitif.
volatile sendiri tidak benar. Utas yang mencoba mengambil dan kemudian menulis kembali nilai yang dimodifikasi masih dapat bertentangan dengan utas lainnya yang melakukan hal yang sama.
sumber
Saya melakukan beberapa tes untuk melihat bagaimana teori itu bekerja: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html . Tes saya lebih fokus pada CompareExchnage tetapi hasil untuk Peningkatan serupa. Saling bertautan tidak perlu lebih cepat dalam lingkungan multi-cpu. Ini adalah hasil tes untuk Peningkatan pada server 16 CPU berusia 2 tahun. Ingatlah bahwa tes ini juga melibatkan baca aman setelah peningkatan, yang khas di dunia nyata.
sumber
Saya memberi jawaban Jon Skeet kedua dan ingin menambahkan tautan berikut untuk semua orang yang ingin tahu lebih banyak tentang "volatile" dan Saling bertautan:
Atomicity, volatilitas, dan immutabilitas berbeda, bagian satu - (Petualangan Coding Eric Lippert yang Luar Biasa)
Atomitas, volatilitas, dan imutabilitas berbeda, bagian kedua
Atomitas, volatilitas, dan imutabilitas berbeda, bagian ketiga
Sayonara Volatile - (snapshot Wayback Machine dari Joe Duffy, seperti yang muncul pada 2012)
sumber
Saya ingin menambah disebutkan dalam jawaban yang lain perbedaan antara
volatile
,Interlocked
danlock
:Kata kunci yang mudah menguap dapat diterapkan ke bidang jenis ini :
sbyte
,byte
,short
,ushort
,int
,uint
,char
,float
, danbool
.byte
,sbyte
,short
, ushort,int
atauuint
.IntPtr
danUIntPtr
.Tipe lain , termasuk
double
danlong
, tidak dapat ditandai "volatil" karena membaca dan menulis ke bidang tipe tersebut tidak dapat dijamin sebagai atom. Untuk melindungi akses multi-utas ke jenis bidang tersebut, gunakanInterlocked
anggota kelas atau lindungi akses menggunakanlock
pernyataan.sumber