13

Giả sử tôi có một biến thành viên trong một lớp học (với nguyên tử đọc/kiểu dữ liệu ghi):C# biến tươi

bool m_Done = false; 

Và sau đó tôi có thể tạo một nhiệm vụ để thiết lập nó là true:

Task.Run(() => m_Done = true); 

Tôi không quan tâm khi nào chính xác m_Done sẽ được đặt thành true. Câu hỏi của tôi là tôi có bảo đảm bằng đặc tả ngôn ngữ C# và thư viện song song nhiệm vụ cuối cùng m_Done sẽ đúng nếu tôi truy cập nó từ một chuỗi khác?
Ví dụ:

if(m_Done) { // Do something } 

Tôi biết rằng việc sử dụng ổ khóa sẽ giới thiệu những rào cản bộ nhớ cần thiết và m_Done sẽ được hiển thị như đúng sau. Ngoài ra tôi có thể sử dụng Volatile.Write khi thiết lập biến và Volatile.Read khi đọc nó. Tôi thấy rất nhiều mã được viết theo cách này (không có khóa hoặc dễ bay hơi) và tôi không chắc liệu mã đó có chính xác hay không.

Lưu ý rằng câu hỏi của tôi không nhắm mục tiêu triển khai cụ thể C# hoặc .Net, nó đang nhắm mục tiêu đặc điểm kỹ thuật. Tôi cần biết liệu mã hiện tại có hoạt động tương tự không nếu chạy trên x86, x64, Itanium hoặc ARM.

+0

tôi không thể không cảm thấy rằng điểm của 'async'/'đang chờ đợi' là để tránh những câu hỏi khó này ... – Aron

+0

@Aron Không, không phải vậy. Nếu mã của bạn sử dụng 'async'-'await' làm (hoặc có thể) chạy trên nhiều luồng, bạn vẫn cần phải đồng bộ hóa đúng cách. – svick

+0

@svick Tùy thuộc. Bạn có thể sử dụng 'await' để đồng bộ hóa và sắp xếp kết quả cho chuỗi mà" sở hữu "đối tượng đó, và có THAT thiết lập thuộc tính. – Aron

Trả lời

23

Tôi không quan tâm khi nào chính xác m_Done sẽ được đặt thành true. Câu hỏi của tôi là tôi có bảo đảm bằng đặc tả ngôn ngữ C# và thư viện Task song song mà cuối cùng m_Done sẽ đúng nếu tôi truy cập nó từ một luồng khác?

số

Các đọc của m_Done là non-volatile và do đó có thể được di chuyển tùy ý xa về phía sau trong thời gian, và kết quả có thể được lưu trữ. Kết quả là, nó có thể được quan sát là false trên mọi lần đọc cho mọi thời đại.

Tôi cần biết liệu mã hiện tại sẽ hoạt động tương tự nếu chạy trên x86, x64, Itanium hoặc ARM.

Không có sự đảm bảo nào được thực hiện bởi đặc tả rằng mã sẽ được quan sát để thực hiện tương tự trên các mô hình bộ nhớ mạnh (x86) và yếu (ARM).

Đặc điểm kỹ thuật khá rõ ràng về những gì đảm bảo được thực hiện về đọc và ghi không dễ bay hơi: chúng có thể được sắp xếp lại tùy ý trên các chủ đề khác nhau trong trường hợp không có các sự kiện đặc biệt nhất định như khóa.

Đọc thông số kỹ thuật để biết chi tiết, đặc biệt là chút về tác dụng phụ khi chúng liên quan đến truy cập dễ bay hơi. Nếu bạn có thêm câu hỏi sau đó, sau đó đăng câu hỏi mới. Đây là thứ rất phức tạp.

Ngoài ra câu hỏi giả định rằng bạn đang bỏ qua các cơ chế hiện có để xác định rằng một nhiệm vụ được hoàn thành, và thay vào đó là cuộn của riêng bạn. Các cơ chế hiện có được thiết kế bởi các chuyên gia; sử dụng chúng.

Tôi thấy rất nhiều mã được viết theo cách này (không có khóa hoặc dễ bay hơi) và tôi không chắc liệu mã đó có chính xác hay không.

Nó gần như chắc chắn là không.

Một bài tập tốt để gây ra cho người viết mã mà là thế này:

static volatile bool q = false; 
static volatile bool r = false; 
static volatile bool s = false; 
static volatile bool t = false; 
static object locker = new object(); 

static bool GetR() { return r; } // No lock! 
static void SetR() { lock(locker) { r = true; } } 

static void MethodOne() 
{ 
    q = true; 
    if (!GetR()) 
    s = true; 
} 

static void MethodTwo() 
{ 
    SetR(); 
    if (!q) 
    t = true; 
} 

Sau khi khởi tạo của các trường, MethodOne được gọi từ một thread, MethodTwo được gọi từ khác. Lưu ý rằng tất cả mọi thứ là dễ bay hơi và rằng ghi vào r không chỉ là dễ bay hơi, nhưng hoàn toàn có rào chắn. Cả hai phương pháp đều hoàn thành bình thường. Có thể sau đó cho s và t để bao giờ cả hai được quan sát là đúng trên thread đầu tiên? Có thể trên x86? Nó xuất hiện không; nếu chuỗi đầu tiên thắng cuộc đua thì t vẫn là false và nếu chuỗi thứ hai thắng thì s vẫn là false; phân tích này là sai. Tại sao? (Gợi ý: x86 được phép viết lại như thế nào MethodOne?)

Nếu bộ mã hóa không thể trả lời câu hỏi này thì hầu như không thể lập trình chính xác với biến động và không được chia sẻ bộ nhớ trên các chủ đề không có khóa.

+0

Những gì tôi đã hiểu là ở trên chương trình chúng ta có bốn kết quả có thể cho kết hợp s, t giá trị mỗi là đúng/sai, và có thể chỉ vì sử dụng từ khóa dễ bay hơi, một biến khác sẽ được lưu trữ. không bao giờ đọc bằng phương pháp khác, ngay cả khi nó được thay đổi, nhưng gây ra vấn đề với logic dự kiến. Để làm cho mọi thứ thậm chí còn chắc chắn hơn, các hướng dẫn chuẩn như khóa có thể được sử dụng để đảm bảo đảm bảo hoàn toàn cho giải pháp cuối cùng –

+0

@MrinalKamboj: Bạn có thể mô tả * chính xác * trình tự đọc và viết nào dẫn đến s và cả hai đều đúng không? Giả sử thread one thắng cuộc đua và gọi GetR trước khi thread two gọi SetR. q là đúng, thì s là đúng. Sau đó MethodTwo gọi SetR và q là false, vì vậy t vẫn sai. Tương tự như vậy nếu thread two thắng cuộc đua thì có vẻ như t phải đúng nhưng s phải sai. Phân tích của tôi ở đây là không chính xác. Có thể là s và t đều đúng. ** Sai lầm ở đâu **? –

+0

@ Eric Lippert cả s, t chỉ có thể đúng trong trường hợp, khi mã thực thi sao cho nó có thể thực thi điều kiện if trên một trong hai luồng trước biến r, q, được đánh giá nếu được đặt lại trên một luồng khác. Do bộ xử lý phát hiện rằng một biến được đánh giá trên một luồng được sửa đổi trên một biến khác và nó không thể được lưu trữ do chỉ thị dễ bay hơi, do đó nó sắp xếp lại chuỗi các cuộc gọi nếu (! GetR()) và if (! Q) đứng trước SetR () và q = true. Nếu không, nếu chúng ta thấy một cách hợp lý, như bạn đã gợi ý nó sẽ luôn là một sự thật và một sai. Xin vui lòng cho tôi biết nếu hoàn toàn tắt nhãn hiệu. –

4

thử mã này, xây dựng phát hành, chạy mà không có Visual Studio:

class Foo 
{ 
    private bool m_Done = false; 

    public void A() 
    { 
     Task.Run(() => { m_Done = true; }); 
    } 

    public void B() 
    { 
     for (; ;) 
     { 
      if (m_Done) 
       break; 
     } 

     Console.WriteLine("finished..."); 
    } 
} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     var o = new Foo(); 
     o.A(); 
     o.B(); 

     Console.ReadKey(); 
    } 
} 

bạn có cơ hội tốt để nhìn thấy nó chạy mãi mãi

+1

Một ví dụ tốt, thêm một biến động thực sự sẽ sửa chữa vấn đề, xin vui lòng sửa tôi nếu tôi sai –

+0

@Mrinal Kamboj có, dễ bay hơi sẽ ngăn chặn bộ nhớ đệm giá trị của m_Done, gây ra vấn đề chính trong ví dụ. –