Мьютексы, критические участки кода и взаимоблокировки

Мьютексы, критические участки кода и взаимоблокировки

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

Взаимоблокировки являются одним из наиболее распространенных и коварных дефектов синхронизации и часто возникают, когда должны быть одновременно блокированы (lock) два и более мьютекса. Рассмотрим следующую задачу:

• Имеется два связных списка, список А и список В, каждый из которых содержит идентичные структуры и поддерживается рабочими потоками. 

• Для одного класса элементов списка корректность операции зависит от того факта, что данный элемент X находится или отсутствует одновременно в обоих списках. Здесь мы имеем дело с инвариантом, который неформально можно выразить так: "X либо находится в обоих списках, либо не находится ни в одном из них".

• В других ситуациях допускается нахождение элемента только в одном из списков, но не в обоих одновременно. Мотивация. Указанными списками могут быть списки сотрудников отделов А и В, когда некоторым сотрудникам разрешена работа одновременно в двух отделах.

• В связи с вышеизложенным для обоих списков требуются различные мьютексы (объекты CS), но при добавлении или удалении общих элементов списков блокироваться должны одновременно оба мьютекса. Использование только одного мьютекса оказало бы отрицательное влияние на производительность, препятствуя независимому параллельному обновлению двух списков, поскольку мьютекс оказался бы "слишком большим".

Ниже приведен пример возможной реализации функций рабочего потока, предназначенных для добавления и удаления общих элементов списков:

static struct {

 /* Инвариант: действительность списка. */

 HANDLE guard; /* Дескриптор мьютекса. */

 struct ListStuff;

 } ListA, ListB;

DWORD WINAPI AddSharedElement(void *arg) /* Добавляет общий элемент в списки А и В. */

{ /* Инвариант: новый элемент либо находится в обоих списках, либо не находится ни в одном из них. */

 WaitForSingleObject(ListA.guard, INFINITE);

 WaitForSingleObject(ListB.guard, INFINITE);

 /* Добавить элемент в оба списка … */

 ReleaseMutex(ListB.guard);

 ReleaseMutex(ListA.guard);

 return 0;

}

DWORD WINAPI DeleteSharedElement(void *arg) /* Удаляет общий элемент из списков А и В. */

{

 WaitForSingleObject(ListB.guard, INFINITE);

 WaitForSingleObject(ListA.guard, INFINITE);

 /* Удалить элемент из обоих списков … */

 ReleaseMutex(ListB.guard);

 ReleaseMutex(ListA.guard);

 return 0;

} 

С учетом ранее данных рекомендаций этот код выглядит вполне корректным. Однако вытеснение потока AddSharedElement сразу же после того, как он блокирует список А, и непосредственно перед тем, как он попытается заблокировать список В, приведет к взаимоблокировке потоков, если поток DeleteSharedElement начнет выполняться до того, как возобновится выполнение потока AddSharedElement. Каждый из потоков владеет мьютексом, который необходим другому потоку, и ни один из потоков не может перейти к вызову функции ReleaseMutex, который разблокировал бы другой поток.

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

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

Гораздо более простой метод, который описывается почти в любом учебнике по ОС, заключается в предварительном определении "иерархии мьютексов" и программировании потоков таким образом, чтобы захват ими мьютексов осуществлялся в строгом соответствии с заданным иерархическим порядком, а освобождение — в обратном порядке. Эта иерархия может устанавливаться произвольно или естественным образом определяться структурой самой задачи, но в любом случае ее должны придерживаться все потоки. В данном примере лишь требуется, чтобы функция удаления мьютекса поочередно ожидала освобождения списков А и В, и тогда взаимоблокировка потоков никогда не случится, если указанная иерархическая очередность будет соблюдаться всеми потоками в любом месте программы.

Еще одним действенным методом снижения риска взаимоблокировки является размещение двух дескрипторов мьютексов в массиве и использование функции WaitForMultipleObjects с флагом fWaitAll, значение которого установлено равным True, вследствие чего поток в результате выполнения одной атомарной операции будет захватывать либо оба мьютекса, либо ни одного. В случае использования объектов CRITICAL_SECTION описанная методика неприменима.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Анализ CIL-кода

Из книги Язык программирования С# 2005 и платформа .NET 2.0. [3-е издание] автора Троелсен Эндрю

Анализ CIL-кода Напомним, что компоновочный блок не содержит специфических для платформы инструкций, а содержит независимый от платформы CIL-код. Когда среда выполнения .NET загружает компоновочный блок в память, этот CIL-код компилируется (с помощью JIT-компилятора) в


Особо критические ситуации

Из книги Очень хороший самоучитель пользователя компьютером. Как самому устранить 90% неисправностей в компьютере и увеличить его возможности автора Колисниченко Денис Николаевич

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


Критические участки кода

Из книги Системное программирование в среде Windows автора Харт Джонсон М

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


Функции взаимоблокировки

Из книги Визуальное моделирование электронных схем в PSPICE автора Хайнеманн Роберт

Функции взаимоблокировки Если все, что требуется — это увеличение, уменьшение или обмен значениями переменных, как в нашем первом простом примере, то функций взаимоблокировки (interlocked functions) вам будет вполне достаточно. Функции взаимоблокировки проще в использовании,


Мьютексы

Из книги XSLT автора Хольцнер Стивен

Мьютексы Объект взаимного исключения (mutual exception), или мьютекс (mutex), обеспечивает более универсальную функциональность по сравнению с объектом CRITICAL_SECTION. Поскольку мьютексы могут иметь имена и дескрипторы, их можно использовать также для синхронизации потоков,


Покинутые мьютексы

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

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


Сравнительный обзор: мьютексы и объекты CRITICAL_SECTION

Из книги Разработка ядра Linux автора Лав Роберт

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


Другие функции взаимоблокировки

Из книги Идеальный программист. Как стать профессионалом разработки ПО автора Мартин Роберт С.

Другие функции взаимоблокировки Ранее уже было продемонстрировано, что функции InterlockedIncrement и InterlockedDecrement могут пригодиться в тех случаях, когда все, что требуется — это выполнение простейших операций над переменными, доступ к которым разделяется несколькими потоками.


14.1. Регулируемые участки

Из книги автора

14.1. Регулируемые участки Фрагмент электронной схемы, обладающий переходной характеристикой типа РТ1, является реализацией одного из наиболее распространенных в технике регулирующих алгоритмов. Техническая установка обладает PT-характеристикой, когда возбуждение


Internet Explorer и участки данных XML

Из книги автора

Internet Explorer и участки данных XML В Internet Explorer есть специальный тег <XML>, при помощи которого можно создавать участки (island) XML. Участок XML может содержать либо сам код XML, либо ссылку на XML-документ.Участки XML упрощают загрузку документов XML и XSL, поэтому их стоит здесь рассмотреть.


4.4. Синхронизация потоков и критические секции

Из книги автора

4.4. Синхронизация потоков и критические секции Программирование потоков — нетривиальная задача, ведь большинство потоков выполняется одновременно. К примеру, невозможно определить, когда система предоставит доступ к процессору одному потоку, а когда — другому.


4.4.3. Взаимоблокировки исключающих семафоров

Из книги автора

4.4.3. Взаимоблокировки исключающих семафоров Исключающие семафоры являются механизмом, позволяющим одному потоку блокировать выполнение другого потока. Это приводит к возникновению нового класса ошибок. называемых взаимоблокировками или тупиковыми ситуациями. Смысл


4.4.7. Взаимоблокировки двух и более потоков

Из книги автора

4.4.7. Взаимоблокировки двух и более потоков Взаимоблокировка происходит, когда два (или более) потока блокируются в ожидании события, наступление которого на самом деле зависит от действий одного из заблокированных потоков. Например, если поток A ожидает изменения


Критические участки и состояние конкуренции за ресурсы

Из книги автора

Критические участки и состояние конкуренции за ресурсы Ветки кода, которые получают доступ к совместно используемыми данным и манипулируют ими, называются критическими участками (critical region). Обычно небезопасно нескольким потокам выполнения одновременно обращаться к


Взаимоблокировки

Из книги автора

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


Принадлежность кода

Из книги автора

Принадлежность кода Один из худших признаков неправильно функционирующей команды – когда каждый программист возводит стену вокруг своего кода и запрещает другим программистам прикасаться к нему. Я был в местах, где программисты даже запрещали другим смотреть на свой