2013-09-21 22 views
5

Chỉ cần chơi xung quanh với đồng thời trong thời gian rảnh rỗi của tôi, và muốn thử ngăn ngừa đọc rách mà không cần sử dụng ổ khóa ở phía người đọc để người đọc đồng thời không can thiệp với nhau.C#/CLR: MemoryBarrier và rách đọc

Ý tưởng là sắp xếp tuần tự ghi qua khóa, nhưng chỉ sử dụng hàng rào bộ nhớ ở mặt đọc. Dưới đây là một sự trừu tượng có thể tái sử dụng đóng gói phương pháp tiếp cận mà tôi đã đưa ra:

public struct Sync<T> 
    where T : struct 
{ 
    object write; 
    T value; 
    int version; // incremented with each write 

    public static Sync<T> Create() 
    { 
     return new Sync<T> { write = new object() }; 
    } 

    public T Read() 
    { 
     // if version after read == version before read, no concurrent write 
     T x; 
     int old; 
     do 
     { 
      // loop until version number is even = no write in progress 
      do 
      { 
       old = version; 
       if (0 == (old & 0x01)) break; 
       Thread.MemoryBarrier(); 
      } while (true); 
      x = value; 
      // barrier ensures read of 'version' avoids cached value 
      Thread.MemoryBarrier(); 
     } while (version != old); 
     return x; 
    } 

    public void Write(T value) 
    { 
     // locks are full barriers 
     lock (write) 
     { 
      ++version;    // ++version odd: write in progress 
      this.value = value; 
      // ensure writes complete before last increment 
      Thread.MemoryBarrier(); 
      ++version;    // ++version even: write complete 
     } 
    } 
} 

Đừng lo lắng về việc tràn biến phiên bản, tôi tránh một cách khác. Vì vậy, là sự hiểu biết của tôi và ứng dụng của Thread.MemoryBarrier chính xác ở trên? Có bất kỳ rào cản nào không cần thiết không?

+0

Nhận xét và mã của bạn (thông tin/phiên bản) không đồng bộ. –

+0

Cảm ơn, đã đăng bài! – naasking

Trả lời

3

Tôi đã xem xét kỹ mã của bạn và nó xuất hiện đúng với tôi. Một điều mà ngay lập tức nhảy ra ngoài với tôi là bạn đã sử dụng một mô hình được thiết lập để thực hiện thao tác khóa thấp. Tôi có thể thấy rằng bạn đang sử dụng version làm loại khóa ảo. Ngay cả các số được phát hành và số lẻ được mua lại. Và kể từ khi bạn đang sử dụng một giá trị gia tăng đơn điệu cho khóa ảo, bạn cũng tránh được ABA problem. Tuy nhiên, điều quan trọng nhất là bạn tiếp tục lặp lại trong khi cố gắng đọc cho đến khi giá trị khóa ảo được xem là giống nhau trước lần đọc bắt đầu so với sau khi hoàn thành. Nếu không, bạn xem xét việc đọc này không thành công và thử lại tất cả. Vì vậy, vâng, công việc cũng được thực hiện trên logic cốt lõi.

Vậy vị trí của bộ tạo rào cản bộ nhớ là gì? Vâng, tất cả điều này có vẻ khá tốt là tốt. Tất cả các cuộc gọi Thread.MemoryBarrier là bắt buộc. Nếu tôi đã phải nit-pick tôi sẽ nói rằng bạn cần thêm một trong các phương pháp Write để nó trông như thế này.

public void Write(T value) 
{ 
    // locks are full barriers 
    lock (write) 
    { 
     ++version;    // ++version odd: write in progress 
     Thread.MemoryBarrier(); 
     this.value = value; 
     Thread.MemoryBarrier(); 
     ++version;    // ++version even: write complete 
    } 
} 

Cuộc gọi được thêm vào đây đảm bảo rằng ++versionthis.value = value không được đổi chỗ. Bây giờ, đặc tả kỹ thuật ECMA cho phép loại lệnh đó sắp xếp lại. Tuy nhiên, việc thực hiện của Microsoft về CLI và phần cứng x86 đều đã có ngữ nghĩa dễ bay hơi khi viết nên nó sẽ không thực sự cần thiết trong hầu hết các trường hợp. Nhưng, những người hiểu biết, có lẽ nó sẽ là cần thiết trên thời gian chạy Mono nhắm mục tiêu cpu ARM.

Trên mặt số Read tôi không thể tìm thấy lỗi nào. Trong thực tế, việc sắp xếp các cuộc gọi bạn có là chính xác nơi tôi sẽ đặt chúng. Một số người có thể tự hỏi tại sao bạn không cần một cái trước khi đọc ban đầu của version. Lý do là bởi vì vòng lặp bên ngoài sẽ bắt được trường hợp khi lần đọc đầu tiên được lưu vào bộ nhớ cache vì giảm bớt chi tiết Thread.MemoryBarrier.

Vì vậy, điều này đưa tôi đến một cuộc thảo luận về hiệu suất. Điều này thực sự nhanh hơn việc lấy khóa cứng trong phương thức Read? Vâng, tôi đã làm một số thử nghiệm khá rộng rãi của mã của bạn để giúp trả lời điều đó. Câu trả lời là dứt khoát có! Điều này là khá nhanh hơn một chút so với tham gia một khóa cứng. Tôi đã thử nghiệm sử dụng Guid làm loại giá trị vì nó là 128 bit và do đó lớn hơn kích thước từ gốc của máy (64 bit). Tôi cũng đã sử dụng một số biến thể khác nhau về số lượng nhà văn và độc giả. Kỹ thuật khóa thấp của bạn nhất quán và vượt trội đáng kể so với kỹ thuật khóa cứng. Tôi thậm chí đã thử một vài biến thể sử dụng Interlocked.CompareExchange để đọc bảo vệ và tất cả chúng đều chậm hơn. Trong thực tế, trong một số trường hợp, nó thực sự chậm hơn so với lấy khóa cứng. Tôi phải trung thực. Tôi hoàn toàn không ngạc nhiên bởi điều này.

Tôi cũng đã thực hiện một số thử nghiệm hợp lệ đáng kể. Tôi đã tạo ra các bài kiểm tra sẽ chạy trong một thời gian khá lâu và không một lần tôi thấy một cuốn sách bị rách. Và sau đó như là một kiểm tra kiểm soát tôi sẽ tinh chỉnh phương pháp Read theo cách như vậy mà tôi biết nó sẽ là không chính xác và tôi chạy thử nghiệm một lần nữa. Lần này, như mong đợi, những lần đọc rách bắt đầu xuất hiện ngẫu nhiên. Tôi chuyển mã trở lại những gì bạn có và những lần đọc bị rách biến mất; một lần nữa, như mong đợi. Điều này dường như để xác nhận những gì tôi đã mong đợi. Tức là, mã của bạn có vẻ chính xác. Tôi không có nhiều môi trường thời gian chạy và phần cứng để thử nghiệm (và tôi cũng không có thời gian) nên tôi không sẵn lòng cho nó 100% con dấu phê duyệt, nhưng tôi nghĩ tôi có thể cho bạn thực hiện hai ngón tay cái cho bây giờ.

Cuối cùng, với tất cả những gì đã nói, tôi vẫn sẽ tránh đặt sản phẩm này vào sản xuất. Vâng, nó có thể đúng, nhưng người tiếp theo phải duy trì mã có lẽ sẽ không hiểu nó. Ai đó có thể thay đổi mã và phá vỡ nó bởi vì họ không hiểu hậu quả của những thay đổi của họ. Bạn phải thừa nhận rằng mã này khá dễ vỡ. Ngay cả sự thay đổi nhỏ nhất cũng có thể phá vỡ nó.

+0

Cảm ơn bạn đã xem xét chi tiết. Tôi thực sự đăng về điều này trên blog của tôi: http://higherlogics.blogspot.ca/2013/09/clr-concurrency-preventing-torn-reads.html và các chức năng này và một số chức năng khác được xây dựng trên chúng nằm trong nguồn mở Sasa của tôi thư viện: https://sourceforge.net/p/sasa/code/ci/default/tree/Sasa/Atomics.cs#l262. Các phiên bản mới nhất đã được chuyển sang sử dụng VolatileRead/VolatileWrite để làm rõ. Bạn thực sự có thể sử dụng chúng để triển khai các nguyên thủy tải liên kết/lưu trữ-điều kiện. – naasking

+0

@naasking: Xem bạn có thể tạo một hoạt động LL/SC và đăng nó lên blog của bạn hay không. Tôi muốn thấy những gì bạn nghĩ ra. Nếu tôi có thời gian, tôi có thể tự mình thử. –

+0

LL/SC đã có trong mã tôi đã liên kết ở trên (cuối trang). Tôi cũng đưa một số mã vào một cấu trúc có thể tái sử dụng để đơn giản hóa API: https://sourceforge.net/p/sasa/code/ci/default/tree/Sasa.Concurrency/LLSC.cs – naasking

Các vấn đề liên quan