Menggambarkan penggunaan kata kunci yang mudah menguap di C #


88

Saya ingin membuat kode program kecil yang secara visual menggambarkan perilaku volatilekata kunci. Idealnya, ini harus menjadi program yang melakukan akses bersamaan ke bidang statis non volatile dan yang mendapat perilaku salah karenanya.

Menambahkan kata kunci yang mudah menguap dalam program yang sama akan menyelesaikan masalah.

Sesuatu yang tidak berhasil saya capai. Bahkan mencoba beberapa kali, mengaktifkan pengoptimalan, dll., Saya selalu mendapatkan perilaku yang benar tanpa kata kunci 'volatile'.

Apakah Anda tahu tentang topik ini? Apakah Anda tahu cara mensimulasikan masalah seperti itu dalam aplikasi demo sederhana? Apakah itu tergantung pada perangkat keras?

Jawaban:


102

Saya telah mencapai contoh kerja!

Ide utama diterima dari wiki, tetapi dengan beberapa perubahan untuk C #. Artikel wiki menunjukkan ini untuk bidang statis C ++, sepertinya C # selalu dengan hati-hati mengkompilasi permintaan ke bidang statis ... dan saya membuat contoh dengan yang tidak statis:

Jika Anda menjalankan contoh ini dalam mode Rilis dan tanpa debugger (yaitu menggunakan Ctrl + F5) maka baris while (test.foo != 255)akan dioptimalkan ke 'while (true)' dan program ini tidak pernah kembali. Tapi setelah menambahkan volatilekata kunci, Anda selalu mendapatkan 'OK'.

class Test
{
    /*volatile*/ int foo;

    static void Main()
    {
        var test = new Test();

        new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start();

        while (test.foo != 255) ;
        Console.WriteLine("OK");
    }
}

5
Diuji pada .NET 4.0, x86 dan x64 - dapat mengonfirmasi bahwa contoh tersebut masih berlaku. Terima kasih! :)
Roman Starkov

1
Menyenangkan! Saya tidak pernah tahu bahwa JIT melakukan pengoptimalan seperti ini dan volatile menonaktifkannya.
usr

3
Untuk lebih eksplisitnya, Anda menambahkan kata kunci "volatile" ke deklarasi foo, bukan?
JoeCool

21

Ya, ini bergantung pada perangkat keras (Anda tidak mungkin melihat masalah tanpa banyak prosesor), tetapi juga bergantung pada implementasi. Spesifikasi model memori dalam spesifikasi CLR mengizinkan hal-hal yang tidak perlu dilakukan oleh implementasi Microsoft CLR.


6

Ini sebenarnya bukan masalah kesalahan yang terjadi ketika kata kunci 'volatile' tidak ditentukan, lebih dari itu kesalahan dapat terjadi jika belum ditentukan. Umumnya Anda akan tahu kapan kasus ini lebih baik daripada kompiler!

Cara termudah untuk memikirkannya adalah bahwa kompilator dapat, jika diinginkan, menyebariskan nilai-nilai tertentu. Dengan menandai nilai sebagai volatile, Anda memberi tahu diri Anda sendiri dan compiler bahwa nilainya mungkin benar-benar berubah (meskipun compiler tidak berpikir demikian). Ini berarti kompilator tidak boleh memasukkan nilai sebaris, menyimpan cache atau membaca nilai lebih awal (dalam upaya untuk mengoptimalkan).

Perilaku ini sebenarnya bukan kata kunci yang sama seperti di C ++.

MSDN memiliki penjelasan singkat di sini . Berikut adalah posting yang mungkin lebih mendalam tentang subjek Volatilitas, Atomicity, dan Interlocking


4

Sulit untuk mendemonstrasikan di C #, karena kode tersebut diabstraksi oleh mesin virtual, sehingga pada satu implementasi mesin ini bekerja dengan baik tanpa volatile, sementara itu mungkin gagal pada yang lain.

Wikipedia memiliki contoh bagus bagaimana mendemonstrasikannya dalam C.

Hal yang sama dapat terjadi di C # jika kompilator JIT memutuskan bahwa nilai variabel tidak dapat berubah dan dengan demikian membuat kode mesin yang bahkan tidak memeriksanya lagi. Jika sekarang utas lain mengubah nilainya, utas pertama Anda mungkin masih terjebak di dalam lingkaran.

Contoh lainnya adalah Busy Waiting.

Sekali lagi, ini bisa terjadi dengan C # juga, tetapi sangat tergantung pada mesin virtual dan kompiler JIT (atau interpreter, jika tidak memiliki JIT ... secara teori, saya pikir MS selalu menggunakan kompiler JIT dan juga penggunaan Mono satu; tetapi Anda mungkin dapat menonaktifkannya secara manual).


4

Inilah kontribusi saya untuk pemahaman kolektif tentang perilaku ini ... Ini tidak banyak, hanya demonstrasi (berdasarkan demo xkip) yang menunjukkan perilaku ayat yang mudah menguap nilai int non-volatile (yaitu "normal"), berdampingan -di samping, dalam program yang sama ... yang saya cari ketika saya menemukan utas ini.

using System;
using System.Threading;

namespace VolatileTest
{
  class VolatileTest 
  {
    private volatile int _volatileInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start();
      while ( _volatileInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_volatileInt="+_volatileInt);
    }
  }

  class NormalTest 
  {
    private int _normalInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start();
      // NOTE: Program hangs here in Release mode only (not Debug mode).
      // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp
      // for an explanation of why. The short answer is because the
      // compiler optimisation caches _normalInt on a register, so
      // it never re-reads the value of the _normalInt variable, so
      // it never sees the modified value. Ergo: while ( true )!!!!
      while ( _normalInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_normalInt="+_normalInt);
    }
  }

  class Program
  {
    static void Main() {
#if DEBUG
      Console.WriteLine("You must run this program in Release mode to reproduce the problem!");
#endif
      new VolatileTest().Run();
      Console.WriteLine("This program will now hang!");
      new NormalTest().Run();
    }

  }
}

Ada beberapa penjelasan singkat yang sangat bagus di atas, serta beberapa referensi yang bagus. Terima kasih untuk semua yang telah membantu saya memahami volatile(setidaknya cukup untuk mengetahui tidak bergantung pada di volatilemana insting pertama saya lock).

Salam, dan Terima kasih untuk SEMUA ikannya. Keith.


PS: saya akan sangat tertarik demo dari permintaan yang asli, yang adalah: "Saya ingin melihat sebuah static int volatil berperilaku dengan benar di mana sebuah static int bertingkah.

Saya telah mencoba dan gagal dalam tantangan ini. (Sebenarnya saya menyerah cukup cepat ;-). Dalam segala hal yang saya coba dengan vars statis, mereka berperilaku "benar" terlepas dari apakah mereka mudah berubah atau tidak ... dan saya ingin penjelasan MENGAPA demikian, jika memang demikian ... Apakah itu kompilator tidak menyimpan nilai dari static vars dalam register ( sebagai gantinya ia menyimpan referensi ke alamat heap itu)?

Tidak, ini bukan pertanyaan baru ... ini adalah upaya untuk mengarahkan komunitas kembali ke pertanyaan awal.


2

Saya menemukan teks berikut oleh Joe Albahari yang sangat membantu saya.

Saya mengambil contoh dari teks di atas yang saya ubah sedikit, dengan membuat bidang volatile statis. Saat Anda menghapus volatilekata kunci, program akan memblokir tanpa batas. Jalankan contoh ini dalam mode Rilis .

class Program
{
    public static volatile bool complete = false;

    private static void Main()
    {           
        var t = new Thread(() =>
        {
            bool toggle = false;
            while (!complete) toggle = !toggle;
        });

        t.Start();
        Thread.Sleep(1000); //let the other thread spin up
        complete = true;
        t.Join(); // Blocks indefinitely when you remove volatile
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.