Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Критические секции. Блокировка. Оператор lock






Критической секцией кода многопоточного приложения будем называть тот фрагмент кода, в котором ведется работа с общим ресурсом потоков. Чтобы решить проблему " гонки данных", достаточно, чтобы в критической секции кода одновременно мог работать только один поток, другие потоки должны стоять в очереди, ожидая, когда поток, выполняющий критическую секцию, закончит свою работу. После чего начать выполнять критическую секцию сможет другой поток. Такая схема работы называется " блокировкой" потоков. Пока один поток работает в критической секции, работа всех других потоков, претендующих на общий ресурс, блокируется. Существуют различные механизмы блокировки.

Рассмотрим простейший механизм блокировки, основанный на использовании оператора языка С# - оператора lock. Пусть в нашем приложении существует несколько критических секций, использующих один и тот же ресурс. Разные потоки могут входить в разные секции. Тем не менее, необходимо все их блокировать за исключением той, где работает активный поток, уже успевший захватить ресурс. Решение проблемы состоит в том, что создается некоторый объект, видимый во всех критических секциях. Обычно это объект универсального типа object с именем, например, locker. Затем каждая критическая секция закрывается оператором lock с ключом locker. Синтаксически конструкция блокировки выглядит так:

lock (locker)

{

< Критическая секция>

}

Семантика конструкции такова. Будем полагать, что объект locker может находиться в двух состояниях - " открыт" или " закрыт". В исходном состоянии объект открыт. Когда некоторый поток достигает критической секции с ключевым объектом locker, то, если объект открыт, поток начинает выполнять критическую секцию, предварительно переключая объект locker в состояние " закрыт". При завершении критической секции объект locker переводится в состояние " открыт". Когда поток достигает критической секции с закрытым объектом locker, то он становится в очередь на выполнение критической секции. Как только locker открывается, первый по очереди поток входит в критическую секцию, блокируя ресурс для других потоков из очереди.

Такой способ блокировки позволяет справиться с проблемой " гонки данных" и писать потокобезопасные приложения.

Клинч

Клинч, дедлок (deadlock), смертельные объятия - разные названия одной из самых серьезных проблем, возникающих при параллельном программировании. Клинч может возникнуть в ситуации, когда два или более параллельно выполняемых потока конкурируют за обладание двумя или более общими ресурсами. При клинче каждый из потоков успевает захватить один из общих ресурсов. Для окончания работы каждому потоку необходимы другие ресурсы, захваченные другими потоками. В результате, никто из потоков не может завершить свою работу, все стоят в очередях, которые не двигаются, - работа замирает – приложение " зависает". Это худшее, что может случиться с приложением.

Плохо, когда приложение завершается в результате возникновения исключительной ситуации, для которой не предусмотрена нормальная обработка. Еще хуже, когда приложение " нормально" завершается, но выдает некорректные результаты. Совсем плохо, когда приложение " зависает", не выдает результатов и завершить его приходится " грубым" способом с помощью внешнего прерывания (например, с помощью диспетчера задач).

Поясним ситуацию клинча на примере двух потоков P1 и P2 и двух ресурсов R1 и R2. Пусть поток P1, входя в критическую секцию, захватывает ресурс R1, блокируя его для использования потоком P2. Аналогично, работая параллельно, поток P2, входя в критическую секцию, захватывает ресурс R2, блокируя его для использования потоком P1. Потоку P1 в какой-то момент работы в критической секции становится необходимым ресурс R2, но этот ресурс заблокирован и поток становится в очередь, прерывая свое выполнение. Симметричная ситуация возникает с потоком P2. Потоки не смогут продолжить выполнение без внешнего вмешательства.

1. Lock (R1) { … lock (R2) {…} }

2. Lock (R2) { … lock (R1) {…} }

Блокировка позволяет спастись от " гонки данных". Обратная сторона блокировки в том, что, спасаясь от гонки, можно попасть в клинч. Для спасения от клинча нужно корректно организовать работу потоков, использующих несколько общих ресурсов. Перечислим некоторые способы:

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

· Если в критических секциях работа с ресурсами ведется последовательно, а не одновременно, то ресурс следует освобождать, как только работа с ним закончена. Это общее правило работы с разделяемыми ресурсами. Во многих случаях оно позволяет избавиться от клинча. Оно не всегда работает, поскольку часто необходимы одновременно несколько ресурсов в каждой из критических секций.

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

· В основном потоке можно использовать другую форму оператора Join для организации ожидания. Наряду с формой void Join() существует форма оператора ожидания bool Join(int t), позволяющая ожидать завершения работы запущенного потока в течение времени, заданного параметром t. Если поток нормально завершается, то функция Join возвращает значение true. Если же истекло время, отпущенное на ожидание, то функция возвращает значение false. Используя этот механизм, основной поток может корректно обработать возникшую ситуацию, возможно связанную с клинчем. В любом случае приложение не зависнет.

· Применение мягких методов блокировки, когда блокируется только запись, но не чтение ресурса. Блокировка, использующая оператор lock, блокирует любую работу с ресурсом. В то же время, если ресурс используется только для чтения, то возможно его одновременное использование. В этом случае гонка данных не приводит к ошибкам, а мягкие методы блокировки, которые рассмотрим чуть ниже, позволяют избежать клинча.

Мягкие методы блокировки. Модель " Читатели и Писатели"

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






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.