Untuk apa kata kunci "volatile" digunakan?

130

Saya membaca beberapa artikel tentang volatilekata kunci tetapi saya tidak dapat menemukan penggunaannya yang benar. Bisakah Anda memberi tahu saya apa yang harus digunakan dalam C # dan di Jawa?

Mircea
sumber
1
Salah satu masalah dengan volatile adalah artinya lebih dari satu hal. Menjadi informasi bagi kompiler untuk tidak melakukan optimasi funky adalah warisan C. Ini juga berarti bahwa hambatan memori harus digunakan pada akses. Tetapi dalam kebanyakan kasus itu hanya merugikan kinerja dan / atau membingungkan orang. : P
AnorZaken

Jawaban:

93

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".

Will A
sumber
@ Tom - sepatutnya dicatat, Pak - dan diubah.
Will A
11
Itu masih jauh lebih halus dari itu.
Tom Hawtin - tackline 7-10
1
Salah. Tidak mencegah caching. Lihat jawaban saya.
doug65536
168

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 imenjadi 6, versi yang dioptimalkan akan tetap mencetak 5.

Kata volatilekunci mencegah optimasi dan cache seperti itu, dan dengan demikian berguna ketika suatu variabel dapat diubah oleh utas lainnya.

Sjoerd
sumber
3
Saya percaya optimasi masih akan valid dengan iditandai sebagai volatile. Di Jawa, ini semua tentang hubungan sebelum terjadi .
Tom Hawtin - tackline 7-10
Terima kasih telah memposting, jadi entah bagaimana volatile memiliki koneksi dengan penguncian variabel?
Mircea
@ Mircea: Itulah yang saya diberitahu bahwa menandai sesuatu sebagai volatile adalah semua tentang: menandai bidang sebagai volatile akan menggunakan beberapa mekanisme internal untuk memungkinkan thread melihat nilai yang konsisten untuk variabel yang diberikan, tetapi ini tidak disebutkan dalam jawaban di atas ... mungkin seseorang bisa mengkonfirmasi ini atau tidak? Terima kasih
npinti
5
@ Soerd: Saya tidak yakin saya mengerti contoh ini. Jika ivariabel 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" finalketika tidak dinyatakan secara eksplisit.
polygenelubricants
1
C # dan java bukan C ++. Ini tidak benar. Itu tidak mencegah caching dan itu tidak mencegah optimasi. Ini adalah tentang baca-beli, dan semantik rilis-toko, yang diperlukan pada arsitektur memori yang dipesan dengan buruk. Ini tentang eksekusi spekulatif.
doug65536
40

Untuk memahami apa yang volatile lakukan terhadap suatu variabel, penting untuk memahami apa yang terjadi ketika variabel tidak volatil.

  • Variabelnya adalah Non-volatile

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.

  • Variabel tidak stabil

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.

Saurabh Patil
sumber
2
Salah. Itu tidak ada hubungannya dengan "mencegah caching". Ini adalah tentang pemesanan ulang, oleh kompiler, ATAU perangkat keras CPU melalui eksekusi spekulatif.
doug65536
37

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 foovariabel anggota di kelas, dan CPU lain memiliki akses ke instance objek yang dirujuk oleh something, mereka mungkin melihat nilai fooberubah sebelum memori yang ditulis dalam Thingkonstruktor 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 fooadalah volatilekemudian toko untuk fooakan memiliki semantik rilis, dan jaminan perangkat keras yang semua menulis sebelum menulis untuk footerlihat ke prosesor sebelum mengizinkan menulis untuk footerjadi.

Bagaimana mungkin bagi para penulis untuk fooditata ulang sedemikian buruk? Jika holding line cache fooada 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 MySingletonkonstruktor 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.

volatiletidak 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.

doug65536
sumber
Penjelasan yang bagus. Juga baik periksa contoh penguncian. Namun, saya masih tidak yakin kapan harus digunakan karena saya khawatir tentang aspek caching. Jika saya menulis implementasi antrian di mana hanya 1 utas akan menulis dan hanya 1 utas akan membaca, dapatkah saya bertahan tanpa kunci dan hanya menandai "pointer" kepala dan ekor saya sebagai tidak stabil? Saya ingin memastikan bahwa baik pembaca dan penulis melihat nilai-nilai terbaru.
nickdu
Keduanya headdan tailharus mudah berubah untuk mencegah produsen dari asumsi tailtidak akan berubah, dan untuk mencegah konsumen dari asumsi headtidak akan berubah. Juga, headharus mudah menguap untuk memastikan bahwa data antrian yang ditulis terlihat secara global sebelum toko headtersebut terlihat secara global.
doug65536
+1, Persyaratan seperti terbaru / "terbaru" sayangnya menyiratkan konsep nilai tunggal yang benar. Pada kenyataannya, dua pesaing dapat melewati garis finish pada waktu yang bersamaan - pada sebuah cpu, dua core dapat meminta penulisan pada waktu yang sama persis . Bagaimanapun, core tidak bergantian melakukan pekerjaan - itu akan membuat multi-core tidak berguna. Pemikiran / desain multi-utas yang baik tidak boleh fokus pada upaya untuk memaksa "terbaru" tingkat rendah - yang pada dasarnya palsu karena kunci hanya memaksa inti untuk secara sewenang - wenang memilih satu pembicara pada satu waktu tanpa keadilan - tetapi lebih baik mencoba untuk merancang jauh butuhkan untuk konsep yang tidak alami seperti itu.
AnorZaken
34

Kata kunci yang mudah menguap memiliki arti yang berbeda di Jawa dan C #.

Jawa

Dari Spesifikasi Bahasa Jawa :

Bidang dapat dinyatakan volatil, dalam hal ini model memori Java memastikan bahwa semua utas melihat nilai yang konsisten untuk variabel.

C #

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.

Krock
sumber
Terima kasih banyak untuk memposting, seperti yang saya pahami di Jawa berfungsi seperti mengunci variabel itu dalam konteks utas, dan dalam C # jika digunakan, nilai variabel dapat diubah tidak hanya dari program, faktor eksternal seperti OS dapat mengubah nilainya ( tidak ada penguncian tersirat) ... Tolong beritahu saya jika saya mengerti benar perbedaan-perbedaan itu ...
Mircea
@Mircea di Java tidak ada penguncian yang terlibat, itu hanya memastikan bahwa nilai variabel volatile yang paling baru akan digunakan.
Krock
Apakah Java menjanjikan semacam penghalang memori, atau apakah itu seperti C ++ dan C # hanya menjanjikan untuk tidak mengoptimalkan referensi?
Steven Sudit
Hambatan memori adalah detail implementasi. Apa yang sebenarnya dijanjikan Java adalah bahwa semua bacaan akan melihat nilai yang ditulis oleh tulisan terbaru.
Stephen C
1
@StevenSudit Ya, jika perangkat keras memerlukan penghalang atau memuat / mengakuisisi atau menyimpan / melepaskan maka ia akan menggunakan instruksi tersebut. Lihat jawaban saya.
doug65536
9

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).

Thorbjørn Ravn Andersen
sumber
1

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.

Subhash Saini
sumber
1
Bisakah Anda ulangi jawaban Anda?
Anirudha Gupta
kata kunci yang mudah menguap akan memberi Anda nilai paling baru daripada nilai yang di-cache.
Subhash Saini
0

Volatile memecahkan masalah konkurensi. Untuk membuat nilai itu sinkron. Kata kunci ini sebagian besar digunakan dalam threading. Ketika beberapa utas memperbarui variabel yang sama.

Shiv Ratan Kumar
sumber
1
Saya tidak berpikir itu "menyelesaikan" masalah. Ini adalah alat yang membantu dalam beberapa keadaan. Jangan mengandalkan volatile untuk situasi di mana kunci diperlukan, seperti dalam kondisi balapan.
Scratte