2012-04-23 3 views
1

У меня есть список, к которому обращаются несколько потоков фона для обновления/чтения. Действия с обновлениями включают в себя как вставки, так и удаления.повысить эффективность одновременного доступа к словарю C#

Чтобы сделать это одновременно без проблем синхронизации, я использую блокировку для частного объекта readonly в классе.

Чтобы свести к минимуму время, необходимое для блокировки списка при чтении его данных, я делаю глубокий клон и возвращаю глубокий клон и разблокирую словарь для вставки/удаления обновлений.

В связи с этим каждое чтение списка увеличивает потребление памяти моей службой.

Следует отметить, что вставки/удаления являются внутренними для класса, содержащего список. Но чтение предназначено для общественного потребления.

Мой вопрос:

Есть ли способ, я могу избежать клонирования списка и до сих пор использовать его одновременно для чтения с использованием блокировки чтения/записи?

public class ServiceCache 
    { 
     private static List<Users> activeUsers; 
     private static readonly object lockObject = new object(); 
     private static ServiceCache instance = new ServiceCache(); 

     public static ServiceCache Instance 
     { 
      get 
      { 
       return instance; 
      } 
     } 

     private void AddUser(User newUser) 
     { 
      lock (lockObject) 
      { 
       //... add user logic 
      } 
     } 

     private void RemoveUser(User currentUser) 
     { 
      lock (lockObject) 
      { 
       //... remove user logic 
      } 
     } 

     public List<Users> ActiveUsers 
     { 
      get 
      { 
       lock (lockObject) 
       { 
        //The cache returns deep copies of the users it holds, not links to the actual data. 
        return activeUsers.Select(au => au.DeepCopy()).ToList(); 
       } 
      } 
     } 
    } 
+0

Если независимые потоки удаления из их глубокой копии из список, как вы гарантируете, что их работа осмысленно синхронизирована? –

+0

Эрик, глубокая копия предназначена только для моментального снимка кеша во времени, и его изменение не должно отражаться в фактическом кеше. – EndlessSpace

+2

Сторона примечания: «Чтобы свести к минимуму время ... я делаю глубокий клон этого»: это точная причина, почему существует фраза «преждевременный корень оптимизации из всего зла». Глубокий клон любого нетривиального объекта вряд ли будет быстрой операцией. Вы всегда должны измерять, чтобы понять, какова ваша «оптимизация». –

ответ

6

Похоже, вам нужно использовать класс ConcurrentDictionary и создать ключ для каждого из объектов, которые вы храните. Тогда это становится так просто, как это для добавления/обновления пользователя:

_dictionary.AddOrUpdate("key", (k, v) => 
    { 
     return newUser; 
    }, (k, v) => 
    { 
     return newUser; 
    }); 

, а затем для удаления, вы могли бы сделать это:

Users value = null; 
_dictionary.TryRemove("key", out value); 

Получение список людей будет супер легко, как хорошо, так как вам просто нужно будет:

return _dictionary.Values.Select(x => x.Value).ToList(); 

Который должен вернуть копию содержимого словаря в тот самый момент.

И пусть среда выполнения .NET позаботится о потоке для вас.

+1

На самом деле вы можете просто вернуть '.Values', который является скопированным на чтение. – SLaks

+0

Интересно, я этого не осознавал! Теперь я получаю полный смысл, когда читаю = D – Tejs

5

Вы можете использовать блокиратор чтения-записи для одновременного чтения.

Однако было бы гораздо быстрее использовать ConcurrentDictionary и неизменяемые потоки значения, а затем избавиться от всей синхронизации.

+0

Имеет значение, даже если блокировка является локальной для этого класса, но я обращаюсь к объекту, который он блокирует снаружи для чтения? – EndlessSpace

+0

Я не уверен, что вы имеете в виду. – SLaks

1

В связи с этим каждое чтение списка увеличивает потребление памяти моей услугой.

Почему? Вызывающие не освобождают ссылку? Они должны, поскольку содержание словаря может измениться.

Что вы делаете с копированием, я думаю очень близко к тому, как параллельная структура данных, например. копирование на запись работает, за исключением того, что вызывающий абонент не может удерживать ссылку.

Несколько других подходов:

  • Верните же копию всех абонентов до сбора модифицируется.Возвращенная коллекция должна быть неизменна

  • Expose всех функциональные звонящие хотел бы от копии и использовать один замок для работы с исходным списком