Saya membaca beberapa artikel tentang volatile
kata kunci tetapi saya tidak dapat menemukan penggunaannya yang benar. Bisakah Anda memberi tahu saya apa yang harus digunakan dalam C # dan di Jawa?
Saya membaca beberapa artikel tentang volatile
kata kunci tetapi saya tidak dapat menemukan penggunaannya yang benar. Bisakah Anda memberi tahu saya apa yang harus digunakan dalam C # dan di Jawa?
Jawaban:
Untuk C # dan Java, "volatile" memberi tahu kompiler bahwa nilai suatu variabel tidak boleh di-cache karena nilainya dapat berubah di luar lingkup program itu sendiri. Compiler kemudian akan menghindari optimasi apa pun yang dapat mengakibatkan masalah jika variabel berubah "di luar kendali".
Pertimbangkan contoh ini:
int i = 5;
System.out.println(i);
Kompiler dapat mengoptimalkan ini untuk hanya mencetak 5, seperti ini:
System.out.println(5);
Namun, jika ada utas lain yang dapat berubah i
, ini adalah perilaku yang salah. Jika utas lainnya berubah i
menjadi 6, versi yang dioptimalkan akan tetap mencetak 5.
Kata volatile
kunci mencegah optimasi dan cache seperti itu, dan dengan demikian berguna ketika suatu variabel dapat diubah oleh utas lainnya.
i
ditandai sebagai volatile
. Di Jawa, ini semua tentang hubungan sebelum terjadi .
i
variabel lokal, toh tidak ada utas lain yang bisa mengubahnya. Jika bidang, kompiler tidak dapat mengoptimalkan panggilan kecuali itu final
. Saya tidak berpikir kompiler dapat membuat optimasi berdasarkan asumsi bahwa bidang "terlihat" final
ketika tidak dinyatakan secara eksplisit.
Untuk memahami apa yang volatile lakukan terhadap suatu variabel, penting untuk memahami apa yang terjadi ketika variabel tidak volatil.
Ketika dua utas A & B mengakses variabel yang tidak mudah menguap, masing-masing utas akan mempertahankan salinan lokal dari variabel dalam cache lokal itu. Setiap perubahan yang dilakukan oleh utas A di cache lokal itu tidak akan terlihat oleh utas B.
Ketika variabel dinyatakan volatil, pada dasarnya ini berarti thread tidak boleh men-cache variabel tersebut atau dengan kata lain thread tidak boleh mempercayai nilai-nilai variabel ini kecuali mereka langsung dibaca dari memori utama.
Jadi, kapan membuat variabel volatile?
Ketika Anda memiliki variabel yang dapat diakses oleh banyak utas dan Anda ingin setiap utas untuk mendapatkan nilai terbaru dari variabel itu bahkan jika nilainya diperbarui oleh utas lain / proses / di luar program.
Bacaan bidang yang mudah menguap telah memperoleh semantik . Ini berarti bahwa dijamin bahwa memori yang dibaca dari variabel volatile akan terjadi sebelum memori berikut dibaca. Ia memblokir kompiler agar tidak melakukan pemesanan ulang, dan jika perangkat keras memerlukannya (CPU yang dipesan dengan buruk), ia akan menggunakan instruksi khusus untuk membuat perangkat keras menyiram pembacaan apa pun yang terjadi setelah pembacaan volatil tetapi secara spekulatif dimulai lebih awal, atau CPU dapat mencegah mereka dikeluarkan sejak awal, dengan mencegah setiap muatan spekulatif terjadi antara masalah perolehan muatan dan pensiunnya.
Menulis bidang volatil memiliki semantik rilis . Ini berarti bahwa setiap memori yang ditulis ke variabel volatil dijamin akan ditunda hingga semua penulisan memori sebelumnya dapat dilihat oleh prosesor lain.
Perhatikan contoh berikut:
something.foo = new Thing();
Jika foo
variabel anggota di kelas, dan CPU lain memiliki akses ke instance objek yang dirujuk oleh something
, mereka mungkin melihat nilai foo
berubah sebelum memori yang ditulis dalam Thing
konstruktor terlihat secara global! Inilah yang dimaksud dengan "memori dengan urutan yang lemah". Ini dapat terjadi bahkan jika kompilator memiliki semua toko di konstruktor sebelum toko foo
. Jika foo
adalah volatile
kemudian toko untuk foo
akan memiliki semantik rilis, dan jaminan perangkat keras yang semua menulis sebelum menulis untuk foo
terlihat ke prosesor sebelum mengizinkan menulis untuk foo
terjadi.
Bagaimana mungkin bagi para penulis untuk foo
ditata ulang sedemikian buruk? Jika holding line cache foo
ada dalam cache, dan toko-toko di constructor melewatkan cache, maka mungkin untuk toko menyelesaikan lebih cepat daripada menulis ke cache cache hilang.
Arsitektur Itanium (mengerikan) dari Intel memiliki memori yang lemah. Prosesor yang digunakan dalam XBox 360 asli memiliki memori yang lemah. Banyak prosesor ARM, termasuk ARMv7-A yang sangat populer memiliki memori yang lemah.
Pengembang sering tidak melihat perlombaan data ini karena hal-hal seperti kunci akan melakukan penghalang memori penuh, pada dasarnya hal yang sama seperti memperoleh dan melepaskan semantik pada saat yang sama. Tidak ada beban di dalam kunci yang dapat dieksekusi secara spekulatif sebelum kunci diperoleh, mereka tertunda sampai kunci diperoleh. Tidak ada toko dapat ditunda di rilis kunci, instruksi yang melepaskan kunci tertunda sampai semua penulisan dilakukan di dalam kunci terlihat secara global.
Contoh yang lebih lengkap adalah pola "Penguncian dua kali". Tujuan dari pola ini adalah untuk menghindari harus selalu mendapatkan kunci untuk malas menginisialisasi objek.
Diambil dari Wikipedia:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking
// mySingleton with 'volatile', which inserts the necessary memory
// barriers between the constructor call and the write to mySingleton.
// The barriers created by the lock are not sufficient because
// the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads
// will acquire the lock. A fence for read-acquire semantics is needed between
// the test of mySingleton (above) and the use of its contents. This fence
// is automatically inserted because mySingleton is marked as 'volatile'.
return mySingleton;
}
}
Dalam contoh ini, toko di MySingleton
konstruktor mungkin tidak terlihat oleh prosesor lain sebelum tokomySingleton
. Jika itu terjadi, utas lain yang mengintip mySingleton tidak akan mendapatkan kunci dan mereka tidak akan selalu mengambil tulisan ke konstruktor.
volatile
tidak pernah mencegah caching. Apa yang dilakukannya adalah menjamin urutan di mana prosesor lain "melihat" menulis. Rilis toko akan menunda toko sampai semua penulisan yang tertunda selesai dan siklus bus telah dikeluarkan memberitahu prosesor lain untuk membuang / menulis kembali garis cache mereka jika mereka memiliki jalur yang relevan di-cache. Perolehan beban akan menyiram pembacaan berspekulasi, memastikan bahwa itu tidak akan menjadi nilai basi dari masa lalu.
head
dan tail
harus mudah berubah untuk mencegah produsen dari asumsi tail
tidak akan berubah, dan untuk mencegah konsumen dari asumsi head
tidak akan berubah. Juga, head
harus mudah menguap untuk memastikan bahwa data antrian yang ditulis terlihat secara global sebelum toko head
tersebut terlihat secara global.
Kata kunci yang mudah menguap memiliki arti yang berbeda di Jawa dan C #.
Dari Spesifikasi Bahasa Jawa :
Bidang dapat dinyatakan volatil, dalam hal ini model memori Java memastikan bahwa semua utas melihat nilai yang konsisten untuk variabel.
Dari C # Referensi tentang kata kunci yang mudah menguap :
Kata kunci yang mudah menguap menunjukkan bahwa bidang dapat dimodifikasi dalam program dengan sesuatu seperti sistem operasi, perangkat keras, atau utas pelaksana secara bersamaan.
Di Jawa, "volatile" digunakan untuk memberi tahu JVM bahwa variabel dapat digunakan oleh banyak utas pada saat yang sama, sehingga optimasi umum tertentu tidak dapat diterapkan.
Khususnya situasi di mana dua utas yang mengakses variabel yang sama berjalan pada CPU terpisah di mesin yang sama. Sangat umum bagi CPU untuk melakukan cache secara agresif terhadap data yang dipegangnya karena akses memori jauh lebih lambat daripada akses cache. Ini berarti bahwa jika data diperbarui dalam CPU1 itu harus segera melalui semua cache dan ke memori utama alih-alih ketika cache memutuskan untuk membersihkan sendiri, sehingga CPU2 dapat melihat nilai yang diperbarui (lagi dengan mengabaikan semua cache di jalan).
Saat Anda membaca data yang tidak mudah menguap, utas pelaksana mungkin atau mungkin tidak selalu mendapatkan nilai yang diperbarui. Tetapi jika objek volatil, utas selalu mendapatkan nilai terbaru.
Volatile memecahkan masalah konkurensi. Untuk membuat nilai itu sinkron. Kata kunci ini sebagian besar digunakan dalam threading. Ketika beberapa utas memperbarui variabel yang sama.