2013-02-01 35 views
5

Hãy nói rằng tôi có một lớp sẽ được gọi từ nhiều chủ đề, và đang đi để lưu trữ một số dữ liệu trong một ImmutableDictionary trong một lĩnh vực riêng trong lớp nàySử dụng Bcl ImmutableDictionary trong lĩnh vực tư nhân

public class Something { 
    private ImmutableDictionary<string,string> _dict; 
    public Something() { 
     _dict = ImmutableDictionary<string,string>.Empty; 
    } 

    public void Add(string key, string value) { 

     if(!_dict.ContainsKey(key)) { 
      _dict = _dict.Add(key,value); 
     } 
    } 
} 

Phải chăng đây là được gọi theo cách như vậy bởi nhiều chủ đề mà bạn sẽ nhận được một lỗi về khóa đã tồn tại trong từ điển?

thread1 kiểm tra từ điển thấy sai thread2 kiểm tra từ điển thấy sai thread1 cho biết thêm giá trị và tham chiếu đến _dict được cập nhật thread2 tăng giá trị, nhưng nó đã được thêm vào vì nó sử dụng tài liệu tham khảo giống nhau không?

+1

Vâng, Tôi tin rằng đó không phải là chủ đề an toàn theo cách bạn mô tả. Bạn có thể cần phải khóa riêng của bạn xung quanh nó. –

+0

Kịch bản mà bạn có nhiều chủ đề cố gắng chèn cùng một mục là gì? Nếu bạn đang đi để song song công việc của bạn, bạn sẽ cần phải phân vùng dữ liệu của bạn trên các chủ đề/máy. –

+0

Nếu nhiệm vụ là '_dict [key] = value;' - thậm chí có thể loại bỏ kiểm tra 'ContainsKey' - đó có thể là luồng an toàn (?) –

Trả lời

3

, giống như chủng tộc như thường lệ áp dụng (cả hai chủ đề đọc, không tìm thấy gì cả, cả hai chủ đề đều viết). An toàn chủ đề không phải là tài sản của cấu trúc dữ liệu mà là toàn bộ hệ thống.

Có một vấn đề khác: Ghi đồng thời vào các phím khác nhau sẽ chỉ mất ghi.

Những gì bạn cần là ConcurrentDictionary. Bạn không thể thực hiện công việc này với công trình bất biến mà không cần khóa bổ sung hoặc vòng lặp CAS.

Cập nhật: Nhận xét đã thuyết phục tôi rằng ImmutableDictionary được sử dụng với vòng lặp CAS để viết thực sự là một ý tưởng hay nếu viết không thường xuyên. Đọc hiệu suất sẽ rất tốt đẹp và viết rẻ như nó được với một cấu trúc dữ liệu đồng bộ.

+0

Sự khác biệt là, với' Dictionary', bạn không thể sử dụng CAS, bạn phải sử dụng một khóa, mà * có thể * tạo sự khác biệt. – svick

+0

@svick nghiêm túc nói rằng bạn * có thể * sử dụng CAS với Từ điển ... Nhưng đó là nitpicking và bạn nói đúng. – usr

+0

Tôi đã xoay xở để lấy khóa trùng lặp bằng cách sao chép từ điển sang biến cục bộ, thực hiện hàm và sau đó gán kết quả trở lại trường riêng tư, nhưng bạn tạo một điểm tốt để ghi đồng thời vào các khóa khác nhau sẽ bị mất. Tôi giả sử trong trường hợp này một số loại ImmutableSet có lẽ là một lựa chọn tốt hơn và sau đó thực hiện một bản sao cục bộ và kết hợp kết quả. – chrisortman

1

Truy cập biến mẫu làm cho phương thức Add() không lặp lại. Sao chép/gán lại cho biến cá thể không thay đổi sự không reentrancy (nó vẫn dễ bị điều kiện chủng tộc). Một ConcurrentDictionary trong trường hợp này sẽ cho phép truy cập mà không có tính nhất quán, nhưng cũng không có khóa. Nếu có nhu cầu cho sự nhất quán 100% trên các chủ đề (không), thì một số loại khóa trên Từ điển là cần thiết. Điều quan trọng là phải hiểu rằng phạm vi hiển thị phạm vi là hai điều khác nhau. Cho dù một biến cá thể là riêng tư hay không có mang về phạm vi của nó, và do đó về an toàn luồng của nó.

3

Bạn hoàn toàn có thể an toàn chỉ trong việc sử dụng từ điển không thay đổi. Bản thân cấu trúc dữ liệu hoàn toàn an toàn với luồng, nhưng bạn áp dụng các thay đổi cho nó trong một môi trường đa luồng phải được viết cẩn thận để tránh mất dữ liệu trong mã của riêng bạn.

Đây là mẫu tôi thường xuyên sử dụng cho một trường hợp như vậy. Nó không yêu cầu khóa, vì đột biến duy nhất chúng tôi làm là gán một bộ nhớ duy nhất. Nếu bạn phải đặt nhiều trường, bạn cần sử dụng khóa.

using System.Threading; 

public class Something { 
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty; 

    public void Add(string key, string value) { 
     // It is important that the contents of this loop have no side-effects 
     // since they can be repeated when a race condition is detected. 
     do { 
      var original = _dict; 
      if (local.ContainsKey(key)) { 
      return; 
      } 

      var changed = original.Add(key,value); 
      // The while loop condition will try assigning the changed dictionary 
      // back to the field. If it hasn't changed by another thread in the 
      // meantime, we assign the field and break out of the loop. But if another 
      // thread won the race (by changing the field while we were in an 
      // iteration of this loop), we'll loop and try again. 
     } while (Interlocked.CompareExchange(ref this.dict, changed, original) != original); 
    } 
} 

Trong thực tế, tôi sử dụng mô hình này để thường xuyên tôi đã xác định một phương pháp tĩnh cho mục đích này:

/// <summary> 
/// Optimistically performs some value transformation based on some field and tries to apply it back to the field, 
/// retrying as many times as necessary until no other thread is manipulating the same field. 
/// </summary> 
/// <typeparam name="T">The type of data.</typeparam> 
/// <param name="hotLocation">The field that may be manipulated by multiple threads.</param> 
/// <param name="applyChange">A function that receives the unchanged value and returns the changed value.</param> 
public static bool ApplyChangeOptimistically<T>(ref T hotLocation, Func<T, T> applyChange) where T : class 
{ 
    Requires.NotNull(applyChange, "applyChange"); 

    bool successful; 
    do 
    { 
     Thread.MemoryBarrier(); 
     T oldValue = hotLocation; 
     T newValue = applyChange(oldValue); 
     if (Object.ReferenceEquals(oldValue, newValue)) 
     { 
      // No change was actually required. 
      return false; 
     } 

     T actualOldValue = Interlocked.CompareExchange<T>(ref hotLocation, newValue, oldValue); 
     successful = Object.ReferenceEquals(oldValue, actualOldValue); 
    } 
    while (!successful); 

    Thread.MemoryBarrier(); 
    return true; 
} 

phương thức Add của bạn sau đó được đơn giản hơn nhiều:

public class Something { 
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty; 

    public void Add(string key, string value) { 
     ApplyChangeOptimistically(
      ref this.dict, 
      d => d.ContainsKey(key) ? d : d.Add(key, value)); 
    } 
} 
Các vấn đề liên quan