Minh họa cách sử dụng từ khóa biến động trong C #


88

Tôi muốn viết mã một chương trình nhỏ minh họa trực quan hành vi của volatiletừ khóa. Lý tưởng nhất, nó phải là một chương trình thực hiện truy cập đồng thời vào một trường tĩnh không bay hơi và có hành vi không chính xác vì điều đó.

Thêm từ khóa dễ bay hơi trong cùng một chương trình sẽ khắc phục được sự cố.

Đó là điều mà tôi đã không quản lý để đạt được. Ngay cả khi thử nhiều lần, bật tối ưu hóa, v.v., tôi luôn nhận được hành vi chính xác mà không có từ khóa 'dễ bay hơi'.

Bạn có ý kiến ​​gì về chủ đề này không? Bạn có biết cách mô phỏng một vấn đề như vậy trong một ứng dụng demo đơn giản không? Nó có phụ thuộc vào phần cứng không?

Câu trả lời:


102

Tôi đã đạt được một ví dụ làm việc!

Ý tưởng chính nhận được từ wiki, nhưng với một số thay đổi cho C #. Bài viết wiki giải thích điều này cho trường tĩnh của C ++, có vẻ như C # luôn biên dịch cẩn thận các yêu cầu thành trường tĩnh ... và tôi làm ví dụ với trường không tĩnh:

Nếu bạn chạy ví dụ này ở chế độ Phát hànhkhông có trình gỡ lỗi (tức là sử dụng Ctrl + F5) thì dòng while (test.foo != 255)sẽ được tối ưu hóa thành 'while (true)' và chương trình này không bao giờ trả về. Nhưng sau khi thêm volatiletừ khóa, bạn luôn nhận được '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
Đã thử nghiệm trên .NET 4.0, cả x86 và x64 - có thể xác nhận rằng ví dụ này vẫn có thể áp dụng được. Cảm ơn! :)
Roman Starkov

1
Đó là tốt đẹp! Tôi chưa bao giờ biết rằng JIT thực hiện loại tối ưu hóa này và tính năng dễ bay hơi vô hiệu hóa nó.
usr

3
Nói một cách rõ ràng, bạn đang thêm từ khóa "dễ bay hơi" vào khai báo của foo, phải không?
JoeCool

21

Có, nó phụ thuộc vào phần cứng (bạn khó có thể gặp sự cố nếu không có nhiều bộ xử lý), nhưng nó cũng phụ thuộc vào việc triển khai. Các thông số kỹ thuật của mô hình bộ nhớ trong thông số kỹ thuật CLR cho phép thực hiện những điều mà Microsoft triển khai CLR không nhất thiết phải làm.


6

Nó không thực sự là một vấn đề lỗi xảy ra khi từ khóa 'variable' không được chỉ định, hơn nữa lỗi có thể xảy ra khi nó chưa được chỉ định. Nói chung, bạn sẽ biết khi nào đây là trường hợp tốt hơn so với trình biên dịch!

Cách dễ nhất để nghĩ về nó là trình biên dịch có thể, nếu nó muốn, nội dòng các giá trị nhất định. Bằng cách đánh dấu giá trị là dễ bay hơi, bạn đang nói với chính mình và trình biên dịch rằng giá trị có thể thực sự thay đổi (ngay cả khi trình biên dịch không nghĩ như vậy). Điều này có nghĩa là trình biên dịch không nên nhập các giá trị trong dòng, giữ bộ nhớ cache hoặc đọc giá trị sớm (trong nỗ lực tối ưu hóa).

Hành vi này không thực sự giống từ khóa như trong C ++.

MSDN có một mô tả ngắn ở đây . Đây có lẽ là một bài viết chuyên sâu hơn về các chủ đề Biến động, Nguyên tử và Sự đan xen


4

Thật khó để chứng minh trong C #, vì mã được trừu tượng hóa bởi một máy ảo, do đó trên một lần triển khai của máy này, nó hoạt động bình thường mà không có biến động, trong khi nó có thể bị lỗi trên một máy khác.

Tuy nhiên, Wikipedia có một ví dụ điển hình về cách chứng minh nó trong C.

Điều tương tự cũng có thể xảy ra trong C # nếu trình biên dịch JIT quyết định rằng giá trị của biến không thể thay đổi và do đó tạo ra mã máy thậm chí không kiểm tra nó nữa. Nếu bây giờ một luồng khác đang thay đổi giá trị, luồng đầu tiên của bạn có thể vẫn bị mắc kẹt trong vòng lặp.

Một ví dụ khác là Chờ đợi Bận rộn.

Một lần nữa, điều này cũng có thể xảy ra với C #, nhưng nó phụ thuộc mạnh mẽ vào máy ảo và trình biên dịch JIT (hoặc trình thông dịch, nếu nó không có JIT ... về lý thuyết, tôi nghĩ MS luôn sử dụng trình biên dịch JIT và Mono cũng sử dụng một; nhưng bạn có thể vô hiệu hóa nó theo cách thủ công).


4

Đây là đóng góp của tôi cho sự hiểu biết chung về hành vi này ... Nó không nhiều, chỉ là một minh chứng (dựa trên bản demo của xkip) cho thấy hành vi của một câu dễ bay hơi một giá trị int không bay hơi (tức là "bình thường"), song song -bên cạnh, trong cùng một chương trình ... đó là những gì tôi đã tìm kiếm khi tôi tìm thấy chủ đề này.

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();
    }

  }
}

Có một số giải thích ngắn gọn thực sự xuất sắc ở trên, cũng như một số tài liệu tham khảo tuyệt vời. Cảm ơn tất cả vì đã giúp tôi quay đầu lại volatile(ít nhất là đủ để biết không dựa vào volatilebản năng đầu tiên của tôi ở đâulock ).

Chúc mừng, và Cảm ơn vì TẤT CẢ những con cá. Keith.


Tái bút: Tôi rất quan tâm đến bản demo của yêu cầu ban đầu, đó là: "Tôi muốn thấy một int biến động tĩnh hoạt động chính xác khi một int tĩnh hoạt động sai.

Tôi đã thử và thất bại trong thử thách này. (Thực ra tôi đã bỏ cuộc khá nhanh ;-). Trong mọi thứ tôi đã thử với các vars tĩnh, chúng hoạt động "chính xác" bất kể chúng có dễ bay hơi hay không ... và tôi muốn giải thích TẠI SAO lại như vậy, nếu thực sự là như vậy ... trình biên dịch không lưu trữ các giá trị của các vars tĩnh trong các thanh ghi (tức là nó lưu vào bộ đệm một tham chiếu đến địa chỉ heap đó)?

Không, đây không phải là một câu hỏi mới ... đó là một nỗ lực để khiến cộng đồng quay trở lại câu hỏi ban đầu.


2

Tôi xem qua văn bản sau đây của Joe Albahari đã giúp tôi rất nhiều.

Tôi lấy một ví dụ từ văn bản trên mà tôi đã thay đổi một chút, bằng cách tạo một trường biến động tĩnh. Khi bạn loại bỏ volatiletừ khóa, chương trình sẽ chặn vô thời hạn. Chạy ví dụ này ở chế độ Phát hành .

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
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.