Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Синхронизация потоков




 

В реальном приложении потоки часто используют совместные ресурсы. Это означает, в частности, что несколько потоков сразу могут обращаться и изменять одну и ту же переменную. Зачастую это может приводить к нежелательным последствиям. Рассмотрим следующий пример. Есть 3 потока. Первый из них добавляет элементы в некоторый список. Остальные 2 потока удаляют элементы из этого списка. Чтобы удаление произошло правильно, необходимо, чтобы в списке был хотя бы один элемент. Поэтому удаляющие потоки используют следующий код:

if (list.Count > 0)

list.RemoveAt(0);

 

Пусть в некоторый момент времени в списке находится единственный элемент. Вполне возможен следующий вариант работы удаляющих потоков:

Квант времени Удаляющий поток 1 Результат работы потока 1 Удаляющий поток 2 Результат работы потока 2
  if (list.Count > 0) true    
      if (list.Count > 0) true
      list.RemoveAt(0); Ok
  list.RemoveAt(0); Exception    

 

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

 

Оператор lock

 

Наиболее простым способом синхронизации работы потоков является использование оператора lock. Пусть вы хотите реализовать шаблон «одиночка». Он позволяет иметь в приложении только один экземпляр класса. Стандартная реализация этого шаблона выглядит следующим образом:

public class Singleton

{

private static Singleton _instance = null;

 

private Singleton() { }

 

public static Singleton Instance

{

get

{

if (_instance == null)

{

_instance = new Singleton();

}

return _instance;

}

}

}

 

Поскольку конструктор класса является закрытым, то получить экземпляр класса возможно только через статическое свойство Instance. Внутри этого свойства создается единственный экземпляр класса, который возвращается всем запрашивающим. Однако данная реализация имеет изъян. Если два потока одновременно вызовут свойство Instance, одновременно проверят, что _instance == null, им обоим будет указано, что экземпляр еще не создан. После этого каждому из них будет создан и возвращен собственный экземпляр класса Singleton, а переменная _instance будет иметь ссылку на последний созданный объект. Таким образом в приложении появятся 2 экземпляра класса Singleton. Но это не то поведение которое нам нужно. Поэтому код свойства Instance нужно модифицировать следующим образом (программа ThreadingLock):

private static object _locker = new object();

public static Singleton Instance

{

get

{

if (_instance == null)

{

lock (_locker)

{

if (_instance == null)

{

_instance = new Singleton();

}

}

}

return _instance;

}

}

 

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

Рассмотрим, как использование lock позволяет решить нашу проблему. Оба потока проверят _instance и узнают, что экземпляр Singleton еще не создан. Тогда они перейдут к блоку lock. Только один из потоков успеет первым заблокировать _locker, а второй поток останется ждать, пока этот объект не будет разблокирован. Первый поток успешно создаст экземпляр Singleton и покинет блок lock. Только в этот момент второй поток сможет войти в этот блок. Теперь становится ясна необходимость во второй проверке переменной _instance внутри блока lock. Если бы этой проверки не было, второй поток, войдя в блок, создал бы еще один экземпляр Singleton, что недопустимо.

В заключении рассмотрения работы оператора lock, хочу привести один полезный прием. Иногда у вас нет объекта вроде _locker, который можно было бы заблокировать. В этом случае можно использовать следующий синтаксис:

lock (typeof(Singleton))

 






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных