Блокировка доступа к объекту для его редактирования - PullRequest
0 голосов
/ 15 октября 2019

Допустим, у меня есть один экземпляр объекта Image, который будет прочитан и записан двумя или более потоками.

Будет ли следующий способ правильной синхронизации доступа?

Я знаю, что более понятным способом было бы использовать lock () {}, но тогда мне понадобится объект блокировки, который есть во всех потоках. доступ к? и я считаю, что было бы плохой практикой блокировать сам объект изображения, поскольку другой код мог бы делать это по совершенно другой причине и вызывать тупик?

 public class Image
{
    public void Lock()
    {
        m_gotLock = false;
        Monitor.Enter(m_lock, ref m_gotLock);
    }

    public void Write()
    {
        // ...
    }

    public void Unlock()
    {
        if (m_gotLock)
        {
            Monitor.Exit(m_lock);
        }
    }

    object m_lock = new object();
    bool m_gotLock = false;
}

class Program
{
    static void Main(string[] args)
    {
        Image image = new Image();

        new Thread(Work).Start(image);
        new Thread(Work).Start(image);
        new Thread(Work).Start(image);
    }

    static void Work(object param)
    {
        var img = (Image)param;
        while(true)
        {
            Thread.Sleep(100);

            img.Lock();
            img.Write();
            img.Unlock();
        }
    }
}

Редактировать: А как насчет этого подхода?

public class Image
{
    public object Lock { get; } = new object();

    public void Write()
    {
        // ...
    }
}

class Program
{
    static void Main(string[] args)
    {
        Image image = new Image();

        new Thread(Work).Start(image);
        new Thread(Work).Start(image);
        new Thread(Work).Start(image);
    }

    static void Work(object param)
    {
        var img = (Image)param;
        while(true)
        {
            Thread.Sleep(100);

            lock(img.Lock)
            {
                img.Write();
            }
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 15 октября 2019

Да, для блокировки вам нужен общий ресурс. Вот как работает синхронизация шаблонов блокировки / мьютекста .

Вы правильно выполнили первую часть, имея выделенный объект мьютекса (m_lock), чтобы указать Monitor на.

Youкажется, есть странная, необъяснимая проблема с использованием оператора блокировки. Вы, кажется, намереваетесь вызвать Monitor.Enter () и .Exit (). Это не имеет абсолютно никакого смысла, делает код более многословным и более склонным к ошибкам. Пожалуйста, просто прекратите это делать.

function or property{
  lock(mutex){
    //do your Work here
  }
}

Это минимальная сумма, которую вы можете сделать для синхронизации.

Это также максимум, который вы можете сделать из этого класса.

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

Это вероятно , почему у нас нет одновременный список - Индекс Race условия. Индекс становится недействительным, потому что кто-то добавил или удалил что-то из него после получения этого индекса.

  • Предотвращение условий гонки Add () и Remove ()? Не проблема. Используйте приведенный выше пример кода.
  • Предотвращение условий гонки по индексу? Нет шансов. Код класса имеет шанс снежного кома во время большого взрыва, чтобы предотвратить это конкретное состояние гонки.

Сумка, Очередь или Стек? Не иметь индекс для использования, только добавить / удалить или эквиваленты. Не проблема. Словарь? Использует ключ. Здесь нет опасности расы. И да, вы можете полностью использовать ConcurrentDictionary<int, T> вместо List.

Array, List или что-то еще с индексом? Только телефонный код может предотвратить этот пожар.

0 голосов
/ 15 октября 2019

Нет, это неправильный способ блокировки, поскольку он не разблокирует объект в случае исключения (как показано в коде в посте) или вообще допускает несовпадение пар блокировки / разблокировки.

     img.Lock();
     img.Write(); // exception causes object to never unlock
     img.Unlock();

Возможные решения для этой конкретной проблемы:

  • требуется try / finally для каждого места, которое вы используете Lock, чтобы разблокировать внутри finally.
  • Вы также можете выставить m_lock как свойство только для чтения и позволить вызывающим абонентам просто использовать оператор lock {...}. Нет особого преимущества скрытия объекта, если вам нужно выставить пару методов в качестве альтернативы, поскольку оба подхода позволяют в конечном итоге получить несовпадающую пару блокировок (также для объекта это требует явных усилий). И есть очевидный недостаток двух методов, поскольку нет синтаксического сахара для написания правильного кода

    public object TheLock { get { return m_lock; } }
    
  • перемещать блокировку внутри каждого метода, который получает доступ к общим данным, чтобы вызывающие не могли понять это неправильнодля одиночного вызова метода.

    public void Write()
    {
        lock(m_lock)
        {
        // …
        }
    }        
    
  • есть метод, который запускает любой код внутри замка 1:

    class Image { ...
      public T SafeOperation<T>(Func<Image, T> operation)
      {
          Lock();
          try 
          {
              return operation(this);
          }
          finally
          {
               Unlock();
          }
      }
    …}
    
    img.SafeOperation(x => x.Write());
    

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

1 Как указывал Кристофер, использование оператора lock будет проще даже для кода внутри класса и пользовательскихМетоды Lock / Unlock не имеют особых преимуществ (так же, как тот же код, сгенерированный для lock { … }. Предоставление объекта блокировки вне класса (для использования lock снаружи) имеет те же проблемы неправильного использования этого объекта, что и Lock / Unlock пара, которая позволяет заблокировать некорректно.

...