2013-12-12 15 views
13

Ở đây trên tràn ngăn xếp Tôi đã found mã mà memoizes chức năng đơn lập luận:Cách thực hiện ghi nhớ chức năng an toàn chỉ trong C#?

static Func<A, R> Memoize<A, R>(this Func<A, R> f) 
{ 
    var d = new Dictionary<A, R>(); 
    return a=> 
    { 
     R r; 
     if (!d.TryGetValue(a, out r)) 
     { 
      r = f(a); 
      d.Add(a, r); 
     } 
     return r; 
    }; 
} 

Trong khi mã này đã làm xong nhiệm của nó đối với tôi, nó không thành công đôi khi chức năng memoized được gọi là từ nhiều luồng đồng thời: phương thức Add được gọi hai lần với cùng một đối số và ném một ngoại lệ.

Làm cách nào để tôi có thể làm cho an toàn ghi nhớ an toàn?

Trả lời

18

Bạn có thể sử dụng ConcurrentDictionary.GetOrAdd mà làm tất cả mọi thứ bạn cần:

static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f) 
{ 
    var cache = new ConcurrentDictionary<A, R>(); 

    return argument => cache.GetOrAdd(argument, f); 
} 

Chức năng f nên được thread-safe chính nó, vì nó có thể được gọi từ nhiều luồng cùng một lúc.

Mã này cũng không đảm bảo rằng hàm f chỉ được gọi một lần cho mỗi giá trị đối số duy nhất. Nó có thể được gọi nhiều lần, trên thực tế, trong môi trường bận rộn. Nếu bạn cần loại hợp đồng này, bạn nên xem các câu trả lời trong số related question này, nhưng được cảnh báo rằng chúng không nhỏ gọn và yêu cầu sử dụng khóa.

+5

Lưu ý rằng 'GetOrAdd' sẽ không ngăn chặn hoàn toàn f được gọi nhiều lần cho một đối số nhất định; nó chỉ đảm bảo rằng kết quả của chỉ * một * của các lời gọi được thêm vào từ điển. Bạn có thể nhận được nhiều hơn một lời gọi trong trường hợp chủ đề kiểm tra bộ nhớ cache đồng thời trước khi giá trị được lưu trữ đã được thêm vào. Nó thường không đáng lo ngại về điều này, nhưng tôi đề cập đến nó trong trường hợp yêu cầu có tác dụng phụ không mong muốn. –

+0

@ JamesWorld Yep, đúng vậy. Câu trả lời đã chỉnh sửa để phản ánh điều đó, cảm ơn bạn! – Gman

+1

Tôi hơi bối rối - không phải 'cache' ở đây là biến cục bộ? Mỗi lần 'ThreadsafeMemoize()' được gọi, nó sẽ không tạo một từ điển mới? – dashnick

2

Giống như Gman đã đề cập ConcurrentDictionary là cách ưu tiên để thực hiện việc này, tuy nhiên nếu điều đó không có sẵn cho câu lệnh đơn giản lock thì sẽ đủ.

static Func<A, R> Memoize<A, R>(this Func<A, R> f) 
{ 
    var d = new Dictionary<A, R>(); 
    return a=> 
    { 
     R r; 
     lock(d) 
     { 
      if (!d.TryGetValue(a, out r)) 
      { 
       r = f(a); 
       d.Add(a, r); 
      } 
     } 
     return r; 
    }; 
} 

Một vấn đề có thể xảy ra khi sử dụng khóa thay vì ConcurrentDictionary là phương pháp này có thể giới thiệu khóa chết vào chương trình của bạn.

  1. Bạn có hai chức năng memoized _memo1 = Func1.Memoize()_memo2 = Func2.Memoize(), nơi _memo1_memo2 là các biến ví dụ.
  2. Thread1 gọi _memo1, Func1 bắt đầu xử lý.
  3. Thread2 gọi _memo2, bên trong Func2 có cuộc gọi đến số _memo1 và chuỗi Thread2.
  4. Quá trình xử lý Func1 của Thread1 được gọi đến _memo2 vào cuối hàm, khối Thread1.
  5. DEADLOCK!

Vì vậy, nếu có thể, hãy sử dụng ConcurrentDictionary, nhưng nếu bạn không thể và bạn sử dụng khóa thay vì không gọi các chức năng Ghi nhớ khác nằm ngoài chức năng bạn đang chạy khi ở trong chức năng Ghi nhớ hoặc bạn mở bản thân bạn có nguy cơ bị deadlocks (nếu _memo1_memo2 là các biến cục bộ thay vì các biến mẫu, thì bế tắc sẽ không xảy ra).

(Lưu ý, hiệu suất có thể được cải thiện đôi chút bằng cách sử dụng ReaderWriterLock nhưng bạn vẫn sẽ có cùng một vấn đề bế tắc.)

1

sử dụng System.Collections.Chung;

Dictionary<string, string> _description = new Dictionary<string, string>(); 
public float getDescription(string value) 
{ 
    string lookup; 
    if (_description.TryGetValue (id, out lookup)) { 
     return lookup; 
    } 

    _description[id] = value; 
    return lookup; 
} 
Các vấn đề liên quan