2010-10-04 32 views
7

Tính năng đọc nguyên tử có được đảm bảo khi bạn kết hợp các hoạt động được khóa liên động với khóa() (và các khóa cấp cao khác) không?Lớp lồng vào nhau có thể được trộn lẫn an toàn với khóa() không?

Tôi quan tâm đến hành vi chung khi trộn các cơ chế khóa như thế này và bất kỳ sự khác biệt nào giữa Int32 và Int64.

private Int64 count; 
private object _myLock; 

public Int64 Count 
{ 
    get 
    { 
    lock(_myLock) 
    { 
     return count; 
    } 
    } 
} 

public void Increment 
{ 
    Interlocked.Increment(ref count); 
} 
+1

Không bao giờ khóa trên 'this', khóa sẽ xung đột với một bên ngoài khóa lớp trên đối tượng. Chỉ bao giờ khóa các thành viên kiểu tham chiếu riêng tư. Nếu bạn không có một thành viên kiểu tham chiếu riêng tư thích hợp của kiểu đúng kiểu vis-a-vis static hoặc instance thì có 'private object _myLock;' hoặc 'private static object _myStaticLock;' chỉ cho mục đích đó. –

+0

@Jon, đã lưu ý và chỉnh sửa –

+3

Tôi vẫn có cảm giác bạn có thể có cá lớn hơn để chiên hơn bao phủ trong câu hỏi này. Tôi hài lòng với câu trả lời của tôi cho đến khi câu hỏi này xảy ra, nhưng nó không giải quyết các vấn đề của luồng A đang làm gì đó trên cơ sở đếm trong khi chuỗi B thay đổi số lượng, bởi vì đó có phải là vấn đề hay không và nếu nó là, phụ thuộc vào nhiều hơn bạn đưa ra ở đây. –

Trả lời

9

Lưu ý: Câu trả lời này (bao gồm cả hai lần chỉnh sửa) đã được đưa ra trước khi câu hỏi được thay đổi thành có độ dài count. Đối với các câu hỏi hiện tại thay vì Thread.VolatileRead Tôi sẽ sử dụng Interlocked.Read, mà cũng có ngữ nghĩa dễ bay hơi và cũng sẽ đối phó với vấn đề đọc 64-bit thảo luận ở đây và đưa vào câu hỏi

Một đọc nguyên tử được đảm bảo mà không cần khóa, bởi vì lần đọc của các giá trị được căn chỉnh đúng 32 bit hoặc ít hơn, mà giá trị count của bạn được đảm bảo là nguyên tử.

Giá trị này khác với giá trị 64 bit nếu nó bắt đầu ở -1 và được đọc trong khi chuỗi khác tăng lên, có thể dẫn đến giá trị là -1 (xảy ra trước khi tăng), 0 (xảy ra sau khi tăng)) hoặc là 4294967295 hoặc -4294967296 (32 bit được ghi bằng 0, 32 bit khác đang chờ ghi).

Tăng nguyên tử Interlocked.Increment có nghĩa là toàn bộ hoạt động gia tăng là nguyên tử. Hãy xem xét số gia tăng đó là khái niệm:

  1. Đọc giá trị.
  2. Thêm một giá trị.
  3. Viết giá trị.

Sau đó, nếu x là 54 và một thread cố gắng để tăng nó trong khi cố gắng khác để đặt nó là 67, hai giá trị đúng có thể là 67 (tăng xảy ra trước, sau đó được viết lên) hoặc 68 (phân xảy ra đầu tiên , sau đó được tăng lên) nhưng tăng không nguyên tử có thể dẫn đến 55 (tăng đọc, chuyển nhượng 67 xảy ra, tăng ghi).

Trường hợp thực tế phổ biến hơn là x là 54 và tăng một luồng và một số lần giảm khác. Ở đây kết quả hợp lệ duy nhất là 54 (một lên, sau đó một xuống, hoặc ngược lại), nhưng nếu không phải nguyên tử thì kết quả có thể là 53, 54 và 55.

Nếu bạn chỉ muốn một số được tăng lên một cách nguyên tử, mã đúng là:

private int count; 

public int Count 
{ 
    get 
    { 
    return Thread.VolatileRead(byref count); 
    } 
} 

public void Increment 
{ 
    Interlocked.Increment(count); 
} 

Nếu bạn muốn hành động theo số đó, bạn cần khóa mạnh hơn. Điều này là do các chủ đề bằng cách sử dụng số lượng có thể trở thành lỗi thời trước khi hoạt động của nó kết thúc. Trong trường hợp này, bạn cần phải khóa tất cả mọi thứ mà quan tâm đến số lượng và tất cả mọi thứ thay đổi nó. Chỉ cần làm thế nào điều này cần được thực hiện (và cho dù nó thậm chí quan trọng để làm ở tất cả) phụ thuộc vào nhiều vấn đề của trường hợp sử dụng của bạn hơn có thể được suy ra từ câu hỏi của bạn.

Chỉnh sửa: Ồ, bạn có thể muốn khóa đơn giản để buộc hàng rào bộ nhớ. Bạn cũng có thể thay đổi triển khai thực hiện Count thành return Thread.VolatileRead(ref count); để đảm bảo bộ đệm CPU bị xóa nếu bạn định tháo khóa. Nó phụ thuộc vào mức độ nghiêm trọng của cache trong trường hợp này. (Một cách khác là làm cho count dễ bay hơi, vì vậy tất cả các lần đọc và ghi sẽ dễ bay hơi. Lưu ý rằng điều này không cần thiết cho các hoạt động Interlocked vì chúng luôn luôn biến động.)

Chỉnh sửa 2: Thật vậy, bạn nên có khả năng muốn đọc điều này dễ bay hơi, rằng tôi đang thay đổi câu trả lời ở trên. Nó có thể bạn sẽ không quan tâm về những gì nó cung cấp, nhưng ít có khả năng hơn nhiều.

+0

Không được tính là được khai báo dễ bay hơi trong trường hợp này? Nếu không, bạn sẽ kết thúc với đăng ký các vấn đề về tiền mặt, phải không? –

+0

@Martin. GMTA, đã được thêm vào như bạn đã nhận xét. Một lần nữa, mặc dù, những gì chúng tôi muốn sẽ phụ thuộc vào một vài điều. VolatileRead sẽ phục vụ những gì trong câu hỏi, nhưng có lẽ 'dễ bay hơi' (theo đề xuất của bạn) sẽ tốt hơn khi các hoạt động khác được thêm vào. Cần bổ sung cho sự hoàn chỉnh rằng API 'Interlocked' có các rào cản bộ nhớ chính xác mà không cần thiết với chúng (thậm chí nói như vậy trong tài liệu cảnh báo bạn nhận được khi bạn gọi chúng byref trên một trường dễ bay hơi). –

+0

Và danh sách người yêu cầu http://stackoverflow.com/questions/3853678/why-lock-is-needed-to-implement-a-readonly-int-property/ vì tôi đang nghĩ đến câu trả lời của tôi cho điều đó, tôi nhận ra nó cũng được áp dụng ở đây. –

3

Đối số được cung cấp cho từ khóa khóa phải là đối tượng dựa trên loại tham chiếu. Trong mã của bạn, đây là loại giá trị, điều này có thể có nghĩa là quyền anh, điều này làm cho tuyên bố khóa vô dụng.

Hoạt động liên khóa là tương đối nguyên tử với một hoạt động khác được liên kết đang chạy trong một chuỗi khác và được áp dụng cho cùng một biến. Không có an toàn luồng, nếu một luồng sử dụng thao tác Liên khóa, và một luồng khác thay đổi cùng một biến bằng cách sử dụng một thuật toán đồng bộ hóa khác, hoặc không đồng bộ hóa.

+0

mã cố định, xin lỗi về điều đó –

2

Câu trả lời cho câu hỏi của bạn là không. Các lockInterlocked không có bất cứ điều gì để làm với nhau và họ không làm việc cùng nhau theo cách bạn đang đề xuất.

Ngoài ra, không chắc chắn điều gì đang xảy ra với mã của bạn. Nó không biên dịch. Bạn không thể khóa trên một loại giá trị. Ngoài ra, Increment() lấy một đối số ref.

+0

mã cố định, xin lỗi về điều đó –

+0

Vâng, điều đó sẽ ít nhất là biên dịch ngay bây giờ. Nhưng chắc chắn, hai loại khóa này sẽ không đồng bộ với nhau. Ngoài ra, bạn không bao giờ nên khóa này, nhưng đó là một cuộc thảo luận hoàn toàn khác nhau. – Bryan

+0

Và bây giờ tôi nghĩ về nó, bạn không cần khóa trên tài sản ở tất cả. Trả về một int là một hoạt động nguyên tử. Nếu đây là tất cả những gì bạn có, bạn có thể tháo khóa (điều này) và bạn sẽ không gặp vấn đề gì. – Bryan

2

Trước tiên, bạn không thể khóa các loại giá trị, như int.

Khi bạn khóa trên một loại giá trị, nó sẽ được đóng hộp vào một đối tượng trước tiên. Vấn đề là nó sẽ được đóng hộp mỗi lần và mỗi lần nó sẽ là một "hộp" khác nhau. Bạn sẽ được khóa trên một đối tượng khác nhau mỗi lần, làm cho khối khóa vô dụng.

Vấn đề đó sang một bên, giả sử bạn khóa trên loại tham chiếu và bạn sử dụng lớp được khóa liên động trên một chuỗi khác. Sẽ không có sự đồng bộ giữa các luồng. Khi bạn muốn đồng bộ hóa, phải sử dụng cùng một cơ chế trên cả hai luồng.

+0

mã cố định, xin lỗi về điều đó –

3

Tính năng đọc nguyên tử có được đảm bảo khi bạn kết hợp các hoạt động khóa liên động với khóa() (và các khóa cấp cao khác) không?

Một nguyên tử đọc của intluôn đảm bảo không phân biệt khóa của loại nào. Đặc tả C# cho biết rằng số lần đọc của int là luôn là nguyên tử.

Tôi nghĩ rằng câu hỏi thực tế của bạn khác với câu hỏi bạn đã hỏi. Bạn có thể làm rõ câu hỏi?

+0

Tôi dự định đặt một câu hỏi tổng quát hơn về việc trộn lẫn phép ẩn dụ, chỉnh sửa cho phù hợp –

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