Kapan menggunakan kata kunci volatile di C #

Teknik pengoptimalan yang digunakan oleh compiler JIT (just-in-time) dalam Common Language Runtime dapat memberikan hasil yang tidak dapat diprediksi saat program .Net Anda mencoba melakukan pembacaan data non-volatile dalam skenario multithread. Pada artikel ini kita akan melihat perbedaan antara akses memori volatile dan non-volatile, peran kata kunci volatile di C #, dan bagaimana kata kunci volatile harus digunakan.

Saya akan memberikan beberapa contoh kode di C # untuk mengilustrasikan konsep. Untuk memahami cara kerja kata kunci volatile, pertama-tama kita perlu memahami cara kerja strategi pengoptimalan compiler JIT di .Net.

Memahami pengoptimalan compiler JIT

Perlu dicatat bahwa compiler JIT akan, sebagai bagian dari strategi optimasi, mengubah urutan pembacaan dan penulisan dengan cara yang tidak mengubah arti dan hasil akhir program. Ini diilustrasikan dalam potongan kode yang diberikan di bawah ini.

x = 0;

x = 1;

Cuplikan kode di atas dapat diubah menjadi berikut — sambil mempertahankan semantik asli program.

x = 1;

Kompilator JIT juga dapat menerapkan konsep yang disebut "propagasi konstan" untuk mengoptimalkan kode berikut.

x = 1;

y = x;

Potongan kode di atas dapat diubah menjadi berikut — lagi-lagi sambil mempertahankan semantik asli program.

x = 1;

y = 1;

Akses memori volatile vs. non-volatile

Model memori sistem modern cukup rumit. Anda memiliki register prosesor, berbagai level cache, dan memori utama yang digunakan bersama oleh beberapa prosesor. Saat program Anda dijalankan, prosesor dapat menyimpan data ke dalam cache dan kemudian mengakses data ini dari cache saat diminta oleh utas yang menjalankan. Pembaruan dan pembacaan data ini mungkin berjalan pada versi data yang di-cache, sementara memori utama diperbarui di lain waktu. Model penggunaan memori ini memiliki konsekuensi untuk aplikasi multithread. 

Saat satu utas berinteraksi dengan data di cache, dan utas kedua mencoba membaca data yang sama secara bersamaan, utas kedua mungkin membaca versi data lama dari memori utama. Ini karena ketika nilai objek non-volatile diperbarui, perubahan dilakukan di cache dari thread yang menjalankan dan bukan di memori utama. Namun, ketika nilai dari objek volatile diperbarui, tidak hanya perubahan yang dibuat dalam cache dari thread yang menjalankan, tetapi cache ini kemudian dibuang ke memori utama. Dan saat nilai objek volatile dibaca, utas menyegarkan cache-nya dan membaca nilai yang diperbarui.

Menggunakan kata kunci volatile di C #

Kata kunci volatile di C # digunakan untuk menginformasikan compiler JIT bahwa nilai variabel tidak boleh di-cache karena mungkin diubah oleh sistem operasi, perangkat keras, atau thread yang dijalankan secara bersamaan. Oleh karena itu, compiler menghindari penggunaan pengoptimalan apa pun pada variabel yang dapat menyebabkan konflik data, yaitu ke thread berbeda yang mengakses nilai variabel yang berbeda.

Ketika Anda menandai sebuah objek atau variabel sebagai volatile, itu menjadi kandidat untuk membaca dan menulis volatile. Perlu dicatat bahwa di C # semua penulisan memori bersifat volatile terlepas dari apakah Anda menulis data ke objek volatile atau non-volatile. Namun, ambiguitas terjadi saat Anda membaca data. Saat Anda membaca data yang non-volatile, thread yang menjalankan mungkin atau mungkin tidak selalu mendapatkan nilai terbaru. Jika objeknya mudah berubah, utas selalu mendapatkan nilai terbaru.

Anda dapat mendeklarasikan variabel sebagai volatile dengan mendahului dengan volatilekata kunci. Cuplikan kode berikut menggambarkan hal ini.

Program kelas

    {

        publik volatile int i;

        static void Main (string [] args)

        {

            // Tulis kode Anda di sini

        }

    }

Anda dapat menggunakan volatilekata kunci dengan jenis referensi, penunjuk, dan enum apa pun. Anda juga dapat menggunakan pengubah volatile dengan tipe byte, short, int, char, float, dan bool. Perlu dicatat bahwa variabel lokal tidak dapat dideklarasikan sebagai volatile. Saat Anda menentukan objek tipe referensi sebagai volatile, hanya pointer (bilangan bulat 32-bit yang menunjuk ke lokasi di memori tempat objek sebenarnya disimpan) yang volatile, bukan nilai instance. Selain itu, variabel ganda tidak dapat berubah-ubah karena ukurannya 64 bit, lebih besar dari ukuran kata pada sistem x86. Jika Anda perlu membuat variabel ganda mudah menguap, Anda harus membungkusnya di dalam kelas. Anda dapat melakukannya dengan mudah dengan membuat kelas pembungkus seperti yang ditunjukkan pada cuplikan kode di bawah ini.

kelas publik VolatileDoubleDemo

{

    private volatile WrappedVolatileDouble volatileData;

}

kelas publik WrappedVolatileDouble

{

    Data ganda publik {get; set; }

Namun, perhatikan batasan dari contoh kode di atas. Meskipun Anda akan memiliki nilai terbaru dari volatileDatapenunjuk referensi, Anda tidak dijamin akan mendapatkan nilai terbaru dari Dataproperti tersebut. Solusi untuk ini adalah membuat WrappedVolatileDoubletipe tidak dapat diubah.

Meskipun kata kunci volatil dapat membantu Anda dalam keamanan thread dalam situasi tertentu, ini bukanlah solusi untuk semua masalah konkurensi thread Anda. Anda harus tahu bahwa menandai variabel atau objek sebagai volatile tidak berarti Anda tidak perlu menggunakan kata kunci kunci. Kata kunci yang mudah menguap bukanlah pengganti kata kunci kunci. Itu hanya ada untuk membantu Anda menghindari konflik data ketika Anda memiliki beberapa utas yang mencoba mengakses data yang sama.