2015-05-26 6 views
3

У меня есть класс, защищенный потоком, который использует конкретный ресурс, к которому необходимо получить доступ исключительно. В моей оценке не имеет смысла, чтобы вызывающие элементы различных методов блокировали Monitor.Enter или ожидали SemaphoreSlim, чтобы получить доступ к этому ресурсу.Блокировка, ожидаемые, эксклюзивные методы доступа

Например, у меня есть «дорогая» асинхронная инициализация. Так как нет смысла инициализировать несколько раз, будь то из нескольких потоков или одного, несколько вызовов должны немедленно возвращаться (или даже вызывать исключение). Вместо этого нужно создать, init и , затем распространить экземпляр на несколько потоков.

ОБНОВЛЕНИЕ 1:

MyClass использует два NamedPipes в любом направлении. Метод InitBeforeDistribute не является инициализацией, а правильной настройкой соединения в обоих направлениях. Не имеет смысла, чтобы труба была доступна для потоков N, прежде чем вы установили соединение. Как только он настроен, несколько потоков могут отправлять сообщения, но только один может реально читать/записывать в поток. Приношу свои извинения за то, что он запутывает это с плохим названием примеров.

UPDATE 2:

Если InitBeforeDistribute реализован SemaphoreSlim(1, 1) с правильной логикой ОЖИДАНИЯ (вместо сблокированных операций метания исключения), это добавить/Square метод OK практика? Он не выбрасывает избыточное исключение (например, в InitBeforeDistribute), будучи заблокированным?

Ниже будет хороший плохой пример:

class MyClass 
{ 
    private int m_isIniting = 0; // exclusive access "lock" 
    private volatile bool vm_isInited = false; // vol. because other methods will read it 

    public async Task InitBeforeDistribute() 
    { 
     if (Interlocked.Exchange(ref this.m_isIniting, -1) != 0) 
      throw new InvalidOperationException(
       "Cannot init concurrently! Did you distribute before init was finished?"); 

     try 
     { 
      if (this.vm_isInited) 
       return; 

      await Task.Delay(5000)  // init asynchronously 
       .ConfigureAwait(false); 

      this.vm_isInited = true; 
     } 
     finally 
     { 
      Interlocked.Exchange(ref this.m_isConnecting, 0); 
     } 
    } 
} 

Некоторые пункты:

  1. Если есть случай, когда блокирование/ожидание доступа к замку делает совершенный смысл, то этот пример не имеет смысла (есть смысл, то есть).
  2. Поскольку мне нужно ждать в методе, я должен использовать что-то вроде SemaphoreSlim, если я использую «правильную» блокировку. Исходя из Семафор для приведенного выше примера позволяет мне не беспокоиться о том, что удалил класс, как только я покончу с этим. (Я всегда не любил идею утилизации элемент, используемый несколькими потоками. Это незначительные положительный, конечно.)
  3. Если метод вызывается часто там могут быть некоторые показатели выгоды, которые, конечно, должны быть измеренным.

Приведенный выше пример не имеет смысла в ref. в (3), так вот еще один пример:

class MyClass 
{ 
    private volatile bool vm_isInited = false; // see above example 
    private int m_isWorking = 0; // exclusive access "lock" 
    private readonly ConcurrentQueue<Tuple<int, TaskCompletionSource<int>> m_squareWork = 
     new ConcurrentQueue<Tuple<int, TaskCompletionSource<int>>(); 

    public Task<int> AddSquare(int number) 
    { 
     if (!this.vm_isInited) // see above example 
      throw new InvalidOperationException(
       "You forgot to init! Did you already distribute?"); 

     var work = new Tuple<int, TaskCompletionSource<int>(number, new TaskCompletionSource<int>() 
     this.m_squareWork.Enqueue(work); 

     Task do = DoSquare(); 

     return work.Item2.Task; 
    } 

    private async Task DoSquare() 
    { 
     if (Interlocked.Exchange(ref this.m_isWorking, -1) != 0) 
      return; // let someone else do the work for you 

     do 
     { 
      try 
      { 
       Tuple<int, TaskCompletionSource<int> work; 

       while (this.m_squareWork.TryDequeue(out work)) 
       { 
        await Task.Delay(5000)  // Limiting resource that can only be 
         .ConfigureAwait(false); // used by one thread at a time. 

        work.Item2.TrySetResult(work.Item1 * work.Item1); 
       } 
      } 
      finally 
      { 
       Interlocked.Exchange(ref this.m_isWorking, 0); 
      } 
     } while (this.m_squareWork.Count != 0 && 
      Interlocked.Exchange(ref this.m_isWorking, -1) == 0) 
    } 
} 

Существуют некоторые конкретные негативные аспекты этого примера «безблокировочного», что я должен обратить внимание?

Большинство вопросов, относящихся к «незащищенному» коду на SO, обычно советуют против него, заявляя, что это для «экспертов».Редко (я мог ошибаться в этом), я вижу предложения для книг/блогов и т. Д., Которые можно вникать в них, если кто-то будет так склонен. Если есть такие ресурсы, которые я должен изучить, пожалуйста, поделитесь. Любые предложения будут высоко оценены!

+0

У вас есть конкретный вопрос? – i3arnon

+0

Почему вы предпочитаете бросать исключение вместо простого ожидания задачи до ее завершения? – i3arnon

+3

Итак, чтобы убедиться, что я вас понимаю ... у вас есть дорогостоящая процедура инициализации, которая вызывается, когда первый поток обращается к ресурсу. Каков прецедент для последующих потоков, которые пытаются получить доступ к ресурсу? С вашего поста звучит так, будто вы хотите, чтобы они бросали исключения или возвращались немедленно (без получения данных?), Ни один из которых не кажется более подходящим, чтобы просто блокировать до завершения инициализации. Я что-то упускаю? Когда что-то нуждается в эксклюзивном доступе, это похоже на точный прецедент для блокировки. – Tejs

ответ

1

Update: отличная статья, связанная

.: Creating High-Performance Locks and Lock-free Code (for .NET) :.


  1. Основная точка около lock-free алгоритмы не то, что они для experts.
    Главное, Do you really need lock-free algorythm here? я не могу понять вашу логику здесь:

    Поскольку это не имеет смысла для инициализации более чем один раз, будь то из нескольких потоков или одну, несколько вызовов должен вернуться немедленно (или даже выбросить исключение).

    Почему пользователи не могут просто ждать результата инициализации и использовать ваш ресурс после этого? Если вы можете, просто используйте класс Lazy<T> или даже Asynchronous Lazy Initialization.

  2. Вы действительно должны прочитать о consensus number и CAS-operations и почему это имеет значение при реализации вашего собственного примитива синхронизации.

    В коде вашего используют Interlocked.Exchange метод, который не CAS в реальном времени, так как она всегда обменов значение, и она имеет ряд консенсуса, равный 2. Это означает, что примитив, использующий такую ​​конструкцию, будет корректно работать только для потоков 2 (не в вашей ситуации, но все же 2).

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

  3. Моя любимая книга в связанной области: Writing High-Performance .NET Code от Ben Watson, и мой любимый блог: Stephen Cleary. Если вы можете быть более конкретным о том, какую книгу вы интересуете, я могу добавить еще несколько ссылок.

  4. Никаких замков в программе не делает вашу заявку lock-free. В приложении .NET вы действительно не должны использовать Exceptions для вашего внутреннего потока программы. Подумайте, что инициализирующий поток не намечается какое-то время ОС (по разным причинам, независимо от того, что они точно).

    В этом случае все остальные потоки вашего приложения будут постепенно стираться, пытаясь получить доступ к вашему общему ресурсу. Я не могу сказать, что это код lock-free. Да, в нем нет замков, но не гарантирует правильность программы и, следовательно, она не является блокировкой по определению.

+0

Спасибо! Я хочу прочитать эти вещи (потоки/производительность) в целом, это очень полезно. – LaFleur

0

Искусство многопроцессорных программирования Мориса Херлихи и Нир Шавит, является большим ресурсом для безблокировочного и ждать свободного программирования. lock-free - это гарантия прогресса, отличная от режима программирования, поэтому, чтобы утверждать, что алгоритм блокирован, нужно проверить или показать доказательства гарантии прогресса. без блокировки в простых терминах подразумевает, что блокирование или остановка одного потока не блокирует прогресс других потоков или что, если потоки блокируются бесконечно часто, то есть еще один поток, который делает прогресс бесконечно часто.

+0

Предлагаемая книга не является специфичной для .NET, что позитивно для более широкого понимания. Спасибо! – LaFleur