2017-01-26 7 views
0

Я ищу решение, которое позволяет нескольким потокам читать общий ресурс (разрешено параллелизм), но затем блокирует эти потоки чтения после того, как поток входит в мутирующий блок, чтобы достичь наилучшего из обоих мировых ,Параллельность с чтением, но блокировка с мутированием

Я искал это reference, но, похоже, решение состоит в том, чтобы блокировать чтение и запись потоков.

class Foo { 

    List<string> sharedResource; 

    public void reading() // multiple reading threads allowed, concurrency ok, lock this only if a thread enters the mutating block below. 
    { 

    } 

    public void mutating() // this should lock any threads entering this block as well as lock the reading threads above 
    { 
     lock(this) 
     { 
     } 
    } 
} 

Есть ли такое решение в C#?

Редактировать

Все потоки, входящие в обоих GetMultiton и конструктор должен возвращать тот же экземпляр. хотите, чтобы они были потокобезопасными.

class Foo: IFoo { 
    public static IFoo GetMultiton(string key, Func<IFoo> fooRef) 
    { 
     if (instances.TryGetValue(key, out IFoo obj)) 
     { 
      return obj; 
     } 
     return fooRef(); 
    } 

    public Foo(string key) { 
      instances.Add(key, this); 
    } 
} 

    protected static readonly IDictionary<string, IFoo> instances = new ConcurrentDictionary<string, IFoo>(); 

Использование

Foo.GetMultiton("key1",() => new Foo("key1")); 

ответ

2

Существует предварительно построенной класс для такого поведения ReaderWriterLockSlim

class Foo { 

    List<string> sharedResource; 
    ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); 

    public void reading() // multiple reading threads allowed, concurrency ok, lock this only if a thread enters the mutating block below. 
    { 
     _lock.EnterReadLock(); 
     try 
     { 
      //Do reading stuff here. 
     } 
     finally 
     { 
      _lock.ExitReadLock(); 
     } 
    } 

    public void mutating() // this should lock any threads entering this block as well as lock the reading threads above 
    { 
     _lock.EnterWriteLock(); 
     try 
     { 
      //Do writing stuff here. 
     } 
     finally 
     { 
      _lock.ExitWriteLock(); 
     } 
    } 
} 

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


С вашим обновлением вам вообще не нужны замки. Просто используйте GetOrAdd из ConcurrentDictionary

class Foo: IFoo { 
    public static IFoo GetMultiton(string key, Func<IFoo> fooRef) 
    { 
     return instances.GetOrAdd(key, k=> fooRef()); 
    } 

    public Foo(string key) { 
      instances.Add(key, this); 
    } 
} 

Обратите внимание, что fooRef() можно назвать более чем один раз, но только первый один, чтобы вернуться будет использоваться в качестве результата для всех потоков. Если вы хотите, чтобы fooRef() вызывался только один раз, для этого потребуется немного более сложный код.

class Foo: IFoo { 
    public static IFoo GetMultiton(string key, Func<IFoo> fooRef) 
    { 
     return instances.GetOrAdd(key, k=> new Lazy<IFoo>(fooRef)).Value; 
    } 

    public Foo(string key) { 
      instances.Add(key, new Lazy<IFoo>(()=>this); 
    } 
} 

    protected static readonly IDictionary<string, Lazy<IFoo>> instances = new ConcurrentDictionary<string, Lazy<IFoo>>(); 
+0

Благодаря Скотт для проникновения в суть, я задаюсь вопросом, что 'ConcurrentDictionary' о, скажем, мой sharedResource является' ConcurrentDictionary', должен ли я использовать 'ReadWriteLockSlim' –

+0

Оцените, можете ли вы добавить пример к своему ответу, если sharedResource является' ConcurrentDictionary' –

+0

Если у вас есть параллельный словарь, вы избавитесь от всех блокировок и просто используйте сам словарь. Он уже обрабатывает блокировку для вас. –

0

Решение зависит от ваших требований. Если производительность ReaderWriterLockSlim (обратите внимание, что это примерно в два раза медленнее обычной блокировки в текущей платформе .NET Framework, поэтому максимальная производительность вы можете достичь, если вы редко меняете, а чтение - довольно тяжелая операция, иначе накладные расходы будут более чем прибылью), вы можете попробовать создайте копию данных, измените их и замените их с помощью класса Interlocked (если не требуется иметь самые последние данные в каждом потоке, как только он был изменен).

class Foo 
{ 
    IReadOnlyList<string> sharedResource = new List<string>(); 

    public void reading() 
    { 
     // Here you can safely* read from sharedResource 
    } 

    public void mutating() 
    { 
     var copyOfData = new List<string>(sharedResource); 

     // modify copyOfData here 

     // Following line is correct only in case of single writer:  
     Interlocked.Exchange(ref sharedResource, copyOfData); 
    } 
} 

Преимущества безблокировочного случае:

  • У нас нет никаких блокировок чтения, таким образом мы получаем максимальную производительности.

Недостатки:

  • Мы должны скопировать данные => трафик памяти (распределение, вывоз мусор)
  • Считыватель нити можно наблюдать не самый последние обновления (если он читает ссылку прежде, чем это было обновлен)
  • Если читатель использует ссылку sharedResource несколько раз, мы должны скопировать эту ссылку на локальную переменную через Interlocked.Exchange (если это использование ссылки предполагает, что это та же коллекция)
  • Если sharedResource - это список изменяемых объектов, то мы должны быть осторожны с обновлением этих объектов в mutating, так как читатель может использовать их в тот же момент => в этом случае лучше сделать копии этих объектов
  • Если есть несколько Updater нитей, то мы должны использовать Interlocked.CompareExchange вместо Interlocked.Exchange в mutating и вид петли

Так что, если вы хотите идти безблокировочным, то лучше использовать неизменные объекты. И в любом случае вы будете оплачивать выделение памяти/GC для производительности.
UPDATE
Вот версия, которая позволяет использовать несколько авторов, а также:

class Foo 
{ 
    IReadOnlyList<string> sharedResource = new List<string>(); 

    public void reading() 
    { 
     // Here you can safely* read from sharedResource 
    } 

    public void mutating() 
    { 
     IReadOnlyList<string> referenceToCollectionForCopying; 
     List<string> copyOfData; 

     do 
     { 
      referenceToCollectionForCopying = Volatile.Read(ref sharedResource); 
      copyOfData = new List<string>(referenceToCollectionForCopying); 

      // modify copyOfData here 
     } while (!ReferenceEquals(Interlocked.CompareExchange(ref sharedResource, copyOfData, 
      referenceToCollectionForCopying), referenceToCollectionForCopying)); 
    } 
} 
+0

Привет, Вери, я новичок в .net, не могли бы вы помочь в контексте моего кода, заинтересованного знать вашу перспективу? –

+0

Да, мутация редко в моем случае, и я читаю больше, код очень поможет –