tôi không thực sự nghĩ rằng bạn cần để chứng minh điều này, bạn chỉ cần tham khảo mọi người đến documentation for Dictionary<TKey, TValue>
: từ điển
A có thể hỗ trợ nhiều độc giả đồng thời, miễn là bộ sưu tập là không được sửa đổi. Mặc dù vậy, việc đếm qua bộ sưu tập thực chất là không phải là quy trình an toàn chỉ. Trong trường hợp hiếm hoi mà một liệt kê có liên quan đến quyền ghi, bộ sưu tập phải được khóa trong toàn bộ liệt kê. Để cho phép bộ sưu tập được truy cập bởi nhiều luồng để đọc và viết, bạn phải thực hiện đồng bộ hóa của riêng mình.
Thực tế là một thực tế nổi tiếng (hoặc phải là) mà bạn không thể đọc từ điển trong khi một chủ đề khác đang viết cho nó.Tôi đã nhìn thấy một vài "vấn đề đa luồng kỳ quái" ở đây trên SO, hóa ra tác giả đã không nhận ra rằng điều này không an toàn.
Sự cố không liên quan cụ thể đến khóa được kiểm tra kép, chỉ là từ điển không phải là một lớp an toàn cho chủ đề, ngay cả đối với kịch bản một người viết/độc giả.
Tôi sẽ đi một bước xa hơn và cho bạn thấy lý do tại sao, trong suy nghi, đây không phải là thread-safe:
private int FindEntry(TKey key)
{
// Snip a bunch of code
for (int i = this.buckets[num % this.buckets.Length]; i >= 0;
i = this.entries[i].next)
// Snip a bunch more code
}
private void Resize()
{
int prime = HashHelpers.GetPrime(this.count * 2);
int[] numArray = new int[prime];
// Snip a whole lot of code
this.buckets = numArray;
}
Nhìn vào những gì có thể xảy ra nếu các phương pháp Resize
xảy ra để được chạy trong khi ngay cả một người đọc gọi FindEntry
:
- Chủ đề A: Thêm thành phần, dẫn đến thay đổi kích thước động;
- Chủ đề B: Tính toán độ lệch của nhóm như (mã băm% số lượng nhóm);
- Chủ đề A: Thay đổi các nhóm có kích thước (nguyên tố) khác;
- Chủ đề B: Chọn chỉ mục phần tử từ mảng xô mới tại chỉ số nhóm cũ;
- Con trỏ của chủ đề B không còn giá trị.
Và đây chính xác là điều không thành công trong ví dụ của dtb. Chủ đề Một tìm kiếm cho một khóa là được biết trước để có trong từ điển, nhưng nó không được tìm thấy. Tại sao? Bởi vì phương pháp FindValue
chọn những gì nó nghĩ là xô đúng, nhưng trước khi nó thậm chí còn có cơ hội nhìn vào bên trong, Thread B đã thay đổi các thùng, và bây giờ Thread A đang tìm trong một số nhóm hoàn toàn ngẫu nhiên không chứa hoặc thậm chí dẫn đến mục nhập đúng.
Đạo đức của câu chuyện: TryGetValue
không phải là hoạt động nguyên tử và Dictionary<TKey, TValue>
không phải là lớp an toàn theo chủ đề. Nó không chỉ là viết đồng thời mà bạn cần phải lo lắng; bạn cũng không thể đọc đồng thời. Trong thực tế, vấn đề thực sự chạy sâu hơn rất nhiều, do hướng dẫn sắp xếp lại bởi jitter và CPU, cache cũ, v.v. - không có rào cản bộ nhớ nào được sử dụng ở đây - nhưng điều này phải chứng minh vượt quá sự nghi ngờ rằng có một điều kiện chủng tộc rõ ràng nếu bạn có yêu cầu Add
chạy cùng lúc với yêu cầu TryGetValue
.
@ dtb Tôi chạy mã của bạn và tôi đã không nhận được ngoại lệ ... – Kiril
@Amir Có, Dual Core và Dual Core lõi máy ... mà một trong những nó sẽ thất bại trên? – Kiril
Không gần như ngay lập tức trên máy (lõi tứ) của tôi. Thử nghiệm tốt, +1. – Aaronaught