Чтение онлайн

на главную - закладки

Жанры

Системное программирование в среде Windows

Харт Джонсон М.

Шрифт:

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

Несмотря на то что объекты 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 описанная методика неприменима.

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

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

Поделиться:
Популярные книги

Кротовский, не начинайте

Парсиев Дмитрий
2. РОС: Изнанка Империи
Фантастика:
городское фэнтези
попаданцы
альтернативная история
5.00
рейтинг книги
Кротовский, не начинайте

Вадбольский

Никитин Юрий Александрович
1. Вадбольский
Фантастика:
попаданцы
5.00
рейтинг книги
Вадбольский

Истребители. Трилогия

Поселягин Владимир Геннадьевич
Фантастика:
альтернативная история
7.30
рейтинг книги
Истребители. Трилогия

Законник Российской Империи. Том 4

Ткачев Андрей Юрьевич
4. Словом и делом
Фантастика:
городское фэнтези
альтернативная история
аниме
дорама
5.00
рейтинг книги
Законник Российской Империи. Том 4

Пятнадцать ножевых 4

Вязовский Алексей
4. 15 ножевых
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Пятнадцать ножевых 4

Адаптация

Уленгов Юрий
2. Гардемарин ее величества
Фантастика:
городское фэнтези
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Адаптация

Имперский Курьер. Том 4

Бо Вова
4. Запечатанный мир
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Имперский Курьер. Том 4

Его огонь горит для меня. Том 2

Муратова Ульяна
2. Мир Карастели
Фантастика:
юмористическая фантастика
5.40
рейтинг книги
Его огонь горит для меня. Том 2

Кодекс Крови. Книга VII

Борзых М.
7. РОС: Кодекс Крови
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга VII

Локки 4 Потомок бога

Решетов Евгений Валерьевич
4. Локки
Фантастика:
аниме
фэнтези
5.00
рейтинг книги
Локки 4 Потомок бога

Хозяйственная помощница для идеала

Свободина Виктория
15. Помощница
Любовные романы:
современные любовные романы
эро литература
5.00
рейтинг книги
Хозяйственная помощница для идеала

Газлайтер. Том 19

Володин Григорий Григорьевич
19. История Телепата
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Газлайтер. Том 19

Три `Д` для миллиардера. Свадебный салон

Тоцка Тала
Любовные романы:
современные любовные романы
короткие любовные романы
7.14
рейтинг книги
Три `Д` для миллиардера. Свадебный салон

Студиозус 2

Шмаков Алексей Семенович
4. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
5.00
рейтинг книги
Студиозус 2