2010-11-20 20 views
5

Nhảy khỏi this thread, tôi đang cố gắng để sử dụng ConcurrentDictionary để tái tạo như sau:.NET4.0: Chủ đề An toàn cập nhật của ConcurrentDictionary <TKey, TValue>

public static class Tracker 
{ 
    private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
    private static object myLock = new object(); 

    public static void Add(string bar) 
    { 
     lock(myLock) 
     { 
      if (!foo.ContainsKey(bar)) 
       foo.Add(bar, 0); 

      foo[bar] = foo[bar] + 1; 
     } 
    } 


    public static void Remove(string bar) 
    { 
     lock(myLock) 
     { 
      if (foo.ContainsKey(bar)) 
      { 
       if (foo[bar] > 0) 
        foo[bar] = foo[bar] - 1; 
      } 
     } 
    } 
} 

nỗ lực ban đầu của tôi là:

public static class Tracker2 
{ 
    private static ConcurrentDictionary<string, int> foo = 
     new ConcurrentDictionary<string, int>(); 

    public static void Add(string bar) 
    { 
     foo.AddOrUpdate(bar, 1, (key, n) => n + 1); 
    }  

    public static void Remove(string bar) 
    { 
     // Adding a 0'd item may be ok if it wasn't there for some reason, 
     // but it's not identical to the above Remove() implementation. 
     foo.AddOrUpdate(bar, 0, (key, n) => (n > 0) ? n - 1 : 0); 
    } 
} 

Đây có phải là cách sử dụng chính xác không? Tôi sẽ tránh được những tình huống như:

  1. Chủ đề 1: các cuộc gọi Thêm ("a"), foo [ "a"] tại là 1.
  2. Chủ đề 1 được chuyển ra cho Chủ đề 2.
  3. Chủ đề 2: cuộc gọi Xóa ("a"), foo ["a"] hiện là 0.
  4. Chủ đề 2 được hoán đổi cho Chủ đề 1.
  5. Chủ đề 1: Yêu cầu foo ["a"] và giả định giá trị là 1 nhưng nó thực sự là 0.

Trả lời

1

Phương phápkhông tương đương: trước tiên ban đầu bạn sẽ thêm 0 tại foo[bar] nếu không có gì tồn tại ở đó, sau đó tăng, với tổng kết quả là 1 tại foo[bar]. Cái thứ hai sẽ thêm một 0 nếu không có gì tồn tại ở đó và sẽ chỉ thực hiện số gia tăng cho các cuộc gọi tiếp theo.

Phương thức Remove không tương đương: ban đầu của bạn sẽ không làm gì nếu foo.ContainsKey(bar)false, trong khi thứ hai sẽ thêm giá trị 0 cho khóa đó.

Bạn đã đọc tài liệu cho AddOrUpdate chưa?


EDIT: sau khi bạn chỉnh sửa câu hỏi của bạn rõ ràng hơn "không, nó sẽ không giải quyết được sự cố của bạn". Lý do là phương pháp Remove: hoạt động của nó bây giờ không phải là nguyên tử, vì có hai hoạt động nguyên tử riêng biệt: TryGetValuefoo[bar] = currValue - 1. Các chủ đề khác có thể nhận được ở giữa những hoạt động và gây ra những mâu thuẫn mà bạn đang lo lắng về.

Điểm của các phương pháp như AddOrUpdate hoặc GetOrAdd là thực hiện các hoạt động phổ biến dưới dạng nguyên tử nhất có thể. Thật không may có vẻ như họ đã không nguyên tử trường hợp của bạn với ConcurrentDictionary; không có UpdateIfExists.

Tuy nhiên, tôi chắc chắn rằng sau sẽ giải quyết sự cố của bạn: the Interlocked.Decrement method. Điều này giải quyết trường hợp sau:

  1. Xóa được gọi trên chủ đề 1; foo[bar] có giá trị 2, được lưu trữ trong currValue.
  2. Chủ đề 2 tiếp quản và Add được gọi ở đó; foo[bar] được tăng lên thành 3.
  3. Quay lại chuỗi 1, đặt foo[bar] thành giá trị currValue - 1 = 1.

Tôi đã cố gắng nghĩ đến các trường hợp khác có thể bị hỏng, nhưng không thể ... điều đó có nghĩa là tôi không tốt như những người bình luận khác, những người đang ngủ, mặc dù: P.

EDIT 2: Tôi nghĩ về một vấn đề với việc sử dụng Interlocked.Decrement: nó sẽ không chỉ sụt lần nếu giá trị của nó là tích cực :(

+0

Điểm tốt. Đã chỉnh sửa để phản ánh sự tương đương với việc triển khai từ điển. – Bullines

+0

's_ConcurrentRequests' và' url' là gì? Ngoài ra tôi cập nhật câu trả lời của tôi để chỉ ra một sự tương đương với phương thức 'Add'. – Domenic

+0

Interlocked.Decrement có thể là ok, nhưng tôi sẽ phải đảm bảo các giá trị lớn hơn 0. Điều này có thể là ok, bởi vì các giá trị không bao giờ nên nhỏ hơn 0. Nhưng mẹo sẽ là có cách an toàn để kiểm tra xem giá trị có lớn hơn 0 không. Có lẽ Interlock.Exchange? – Bullines

3

Bạn không thể tránh được những mâu thuẫn bạn đang lo lắng về chỉ bằng cách sử dụng một ConcurrentDictionary. Bạn cần một cái gì đó nhiều, mạnh mẽ hơn và mạnh mẽ để đảm bảo rằng Hãy tự hỏi mình nếu bạn thực sự cần mức độ nhất quán trước khi bắt tay vào việc giải quyết vấn đề. các chủ đề đánh từ điển sẽ không muck nó lên. Nó không đảm bảo bất cứ điều gì về tính nhất quán của các giá trị khi kéo successi vely bởi nhiều chủ đề. Nó không thể giữ cho bạn khỏi bị bắn vào chân.

+0

Trong tình huống này, cách tiếp cận nào sẽ cho phép tôi giữ chân tôi ở trạng thái không bị bắn? :) – Bullines

+0

Về cơ bản bạn cần có cơ chế mua/giải phóng. – jason

+0

Mutex.WaitOne/Mutex.ReleaseMutex có được sử dụng trước/sau các thao tác trong Add() và Remove() để xử lý điều này không? – Bullines

1

Câu trả lời ngắn gọn: Không, concurrentdictionary sẽ không bảo vệ khỏi chuỗi bạn mô tả.

Nhưng sau đó một lần nữa, mã của bạn sẽ không còn trước đó.

Nếu bạn cần đảm bảo rằng một đối tượng trong bộ sưu tập sẽ dính xung quanh trong khoảng thời gian của một chuỗi hoạt động trên một sợi, hãy gán đối tượng đó vào biến cục bộ trong chuỗi và chỉ sử dụng biến cục bộ. Sau đó, mã chuỗi của bạn không quan tâm điều gì xảy ra với đối tượng trong bộ sưu tập.

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