2010-02-03 35 views
82

Trong dịch vụ web asmx đa luồng của tôi, tôi có một trường lớp _allData thuộc kiểu SystemData của riêng tôi, bao gồm một số ít là List<T>Dictionary<T> được đánh dấu là volatile. Dữ liệu hệ thống (_allData) được làm mới một lần trong một thời gian và tôi làm điều đó bằng cách tạo một đối tượng khác có tên là newData và điền vào các cấu trúc dữ liệu của nó bằng dữ liệu mới. Khi hoàn thành công việc tôi chỉ gángán tham chiếu là nguyên tử vậy tại sao Interlocked.Exchange (đối tượng ref, Object) cần thiết?

private static volatile SystemData _allData 

public static bool LoadAllSystemData() 
{ 
    SystemData newData = new SystemData(); 
    /* fill newData with up-to-date data*/ 
    ... 
    _allData = newData. 
} 

này nên làm việc kể từ khi chuyển nhượng là nguyên tử và các chủ đề có tham chiếu đến dữ liệu cũ tiếp tục sử dụng nó và phần còn lại có hệ thống dữ liệu mới ngay sau khi chuyển nhượng. Tuy nhiên, đồng nghiệp của tôi nói rằng thay vì sử dụng volatile từ khóa và sự bảo đảm đơn giản, tôi nên sử dụng InterLocked.Exchange vì ông ấy nói rằng trên một số nền tảng, nó không đảm bảo rằng nhiệm vụ tham chiếu là nguyên tử. Hơn nữa: khi tôi tuyên bố the _allData lĩnh vực như volatile cảnh báo

Interlocked.Exchange<SystemData>(ref _allData, newData); 

sản xuất "một tham chiếu đến một lĩnh vực không ổn định sẽ không được coi là ổn định" Tôi nên suy nghĩ gì về điều này?

Trả lời

141

Có rất nhiều câu hỏi ở đây. Xem xét từng câu hỏi một:

gán tham chiếu là nguyên tử tại sao Interlocked.Exchange (đối tượng ref, Object) cần?

Phân bổ tham chiếu là nguyên tử. Interlocked.Exchange không chỉ thực hiện phép gán tham chiếu. Nó đọc một giá trị hiện tại của một biến, ngăn chặn giá trị cũ và gán giá trị mới cho biến đó, tất cả đều là một hoạt động nguyên tử.

đồng nghiệp của tôi nói rằng trên một số nền tảng, không đảm bảo rằng phân bổ tham chiếu là nguyên tử. Đồng nghiệp của tôi có đúng không?

Không. Tham chiếu được đảm bảo là nguyên tử trên tất cả các nền tảng .NET.

Đồng nghiệp của tôi là lý do từ cơ sở sai. Điều đó có nghĩa là kết luận của họ là không chính xác?

Không nhất thiết. Đồng nghiệp của bạn có thể cho bạn lời khuyên tốt vì những lý do không tốt. Có lẽ có một số lý do khác tại sao bạn nên sử dụng Interlocked.Exchange. Lập trình không khóa là vô cùng khó khăn và thời điểm bạn khởi hành từ các thực hành được thiết lập tốt được các chuyên gia trong lĩnh vực này tán thành, bạn đang ở trong cỏ dại và mạo hiểm những điều kiện chủng tộc tồi tệ nhất. Tôi không phải là một chuyên gia trong lĩnh vực này cũng không phải là một chuyên gia về mã của bạn, vì vậy tôi không thể đưa ra phán quyết theo cách này hay cách khác.

cảnh báo "tham chiếu đến trường dễ bay hơi sẽ không được coi là dễ bay hơi" Tôi nên nghĩ gì về điều này?

Bạn nên hiểu tại sao đây lại là vấn đề chung. Điều đó sẽ dẫn đến một sự hiểu biết về lý do tại sao cảnh báo là không quan trọng trong trường hợp cụ thể này.

Lý do trình biên dịch đưa ra cảnh báo này là vì đánh dấu trường là biến động có nghĩa là "trường này sẽ được cập nhật trên nhiều luồng - không tạo bất kỳ mã nào lưu trữ giá trị của trường này và đảm bảo rằng đọc hoặc viết của trường này không được "di chuyển về phía trước và ngược thời gian" thông qua sự không thống nhất của bộ đệm bộ xử lý. "

(Tôi cho rằng bạn hiểu tất cả những điều đó. Nếu bạn không hiểu rõ ý nghĩa của biến động và cách nó tác động đến ngữ nghĩa bộ nhớ cache của bộ xử lý thì bạn không hiểu nó hoạt động như thế nào và không nên sử dụng dễ bay hơi. Các chương trình không có khóa là rất khó để có được quyền, đảm bảo rằng chương trình của bạn là đúng bởi vì bạn hiểu nó hoạt động như thế nào, không phải do tai nạn.)

Bây giờ giả sử bạn tạo một biến là bí danh của trường biến động chuyển một ref tới trường đó. Bên trong phương thức được gọi, trình biên dịch không có lý do gì để biết rằng tham chiếu cần phải có ngữ nghĩa dễ bay hơi! Trình biên dịch sẽ vui vẻ tạo mã cho phương thức không triển khai các quy tắc cho các trường dễ bay hơi, nhưng biến số trường biến động. Điều đó hoàn toàn có thể phá vỡ logic không khóa của bạn; giả thiết luôn luôn là một trường dễ bay hơi là luôn luôn truy cập với ngữ nghĩa dễ bay hơi. Nó không có ý nghĩa để đối xử với nó như là dễ bay hơi đôi khi và không lần khác; bạn phải luôn luôn nhất quán nếu không bạn không thể đảm bảo tính nhất quán trên các truy cập khác.

Do đó, trình biên dịch cảnh báo khi bạn làm điều này, bởi vì nó có thể sẽ làm hỏng hoàn toàn logic được khóa cẩn thận của bạn.

Tất nhiên, Interlocked.Exchange được viết để mong đợi một trường dễ bay hơi và làm điều đúng. Do đó, cảnh báo là gây hiểu nhầm. Tôi rất tiếc điều này; những gì chúng ta nên làm là thực hiện một số cơ chế theo đó một tác giả của một phương thức như Interlocked.Exchange có thể đặt một thuộc tính trên phương thức nói rằng "phương thức này có một ref thực thi các ngữ nghĩa dễ bay hơi trên biến, để ngăn chặn cảnh báo".Có lẽ trong một phiên bản tương lai của trình biên dịch, chúng ta sẽ làm như vậy.

+4

cảm ơn! câu trả lời hay nhất đến nay! –

+0

Từ những gì tôi đã nghe Interlocked.Exchange cũng bảo đảm rằng một rào cản bộ nhớ được tạo ra. Vì vậy, nếu bạn tạo một đối tượng mới, sau đó gán một vài thuộc tính và lưu trữ đối tượng trong một tham chiếu khác mà không sử dụng Interlocked.Exchange thì trình biên dịch có thể làm rối trật tự của các hoạt động đó, do đó truy cập tham chiếu thứ hai không phải là luồng an toàn. Điều đó có thực sự như vậy không? Liệu nó có ý nghĩa để sử dụng Interlocked.Exchange là loại kịch bản? – Mike

+10

@Mike: Khi nói đến những gì có thể quan sát thấy trong các tình huống đa luồng khóa thấp, tôi là người dốt nát như người tiếp theo. Câu trả lời có lẽ sẽ khác nhau từ bộ xử lý đến bộ vi xử lý. Bạn nên giải quyết câu hỏi của mình cho một chuyên gia hoặc đọc về chủ đề nếu bạn quan tâm. Cuốn sách của Joe Duffy và blog của anh ấy là những nơi tốt để bắt đầu.Quy tắc của tôi: không sử dụng đa luồng. Nếu bạn phải, sử dụng cấu trúc dữ liệu bất biến. Nếu bạn không thể, hãy sử dụng khóa. Chỉ khi bạn * phải * có dữ liệu có thể thay đổi mà không có khóa, bạn nên xem xét kỹ thuật khóa thấp. –

9

Hoặc là đồng nghiệp của bạn bị nhầm lẫn hoặc anh ấy biết điều gì đó mà đặc tả ngôn ngữ C# không.

5.5 Atomicity of variable references:

"Đọc và viết của kiểu dữ liệu sau đây là nguyên tử: bool, char, byte, SByte, ngắn, ushort, uint, int, float, và các loại tài liệu tham khảo."

Vì vậy, bạn có thể viết thư cho tham chiếu dễ bay hơi mà không có nguy cơ bị hỏng giá trị.

Tất nhiên, bạn nên cẩn thận với cách bạn quyết định chuỗi nào sẽ tìm nạp dữ liệu mới, để giảm thiểu rủi ro nhiều hơn một chuỗi tại một thời điểm.

+1

Đúng với điều kiện bộ nhớ được căn chỉnh chính xác. – zebrabox

+2

@guffa: vâng tôi cũng đã đọc nó. điều này rời khỏi câu hỏi ban đầu "chuyển nhượng tham chiếu là nguyên tử vậy tại sao Interlocked.Exchange (đối tượng ref, Object) cần thiết?" unaswered –

+0

@zebrabox: ý của bạn là gì? khi họ không? bạn sẽ làm gì? –

6

Interlocked.Exchange< T >

Thiết lập một biến kiểu T quy định đến một giá trị xác định và trả về giá trị ban đầu, như là một hoạt động nguyên tử.

Nó thay đổi và trả về giá trị ban đầu, nó vô ích bởi vì bạn chỉ muốn thay đổi nó và, như Guffa đã nói, nó đã là nguyên tử.

Trừ khi hồ sơ được chứng minh là nút cổ chai trong ứng dụng của bạn, bạn nên xem xét việc bỏ khóa, dễ hiểu và chứng minh rằng mã của bạn là đúng.

2

Iterlocked.Exchange() không chỉ là nguyên tử, nó cũng sẽ chăm sóc của tầm nhìn bộ nhớ:

Các chức năng đồng bộ hóa sau đang sử dụng các vật chắn thích hợp để đảm bảo bộ nhớ đặt hàng:

Chức năng rằng vào hoặc rời bộ phận quan trọng

Các chức năng báo hiệu đối tượng đồng bộ hóa

Chức năng chờ

chức năng đan cài

Synchronization and Multiprocessor Issues

Điều này có nghĩa rằng ngoài việc Atomicity nó đảm bảo rằng:

  • Đối thread gọi đó là:
    • Không sắp xếp lại các hướng dẫn là thực hiện (bởi trình biên dịch, thời gian chạy hoặc phần cứng).
  • Đối với tất cả chủ đề:
    • Không đọc vào bộ nhớ điều đó xảy ra trước khi hướng dẫn này sẽ thấy sự thay đổi hướng dẫn này thực hiện.
    • Tất cả các lần đọc sau hướng dẫn này sẽ thấy sự thay đổi được thực hiện bởi hướng dẫn này.
    • Tất cả ghi vào bộ nhớ sau khi lệnh này sẽ xảy ra sau khi thay đổi hướng dẫn này đã đạt tới bộ nhớ chính (bằng cách chuyển đổi hướng dẫn này sang bộ nhớ chính khi bộ nhớ của nó hoạt động và không để cho phần cứng tuôn ra theo thời gian).
Các vấn đề liên quan