Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Параллельные потоки.doc
Скачиваний:
2
Добавлен:
03.09.2019
Размер:
188.93 Кб
Скачать

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

Продолжим наш пример и попытаемся найти корректное решение задачи уничтожения объекта потока. Интуитивно можно предположить, что для решения нам потребуется некоторый третейский судья, так как самостоятельно ни приложение, ни поток с задачей не справятся. Таким арбитром может служить объект, известный как "критическая секция". Смысл этого объекта состоит в том, что операционная система гарантирует, что при любом раскладе только один из множества конкурирующих параллельных потоков может попасть внутрь критической секции. Все остальные потоки будут приостановлены операционной системой вплоть до момента, когда поток, захвативший критическую секцию, выйдет из нее. В критической секции можно проверить действительность ссылки, не опасаясь, что она будет нарушена. Это действительно корректное решение, но какой ценой?

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

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

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

Потоки и дескрипторы

Когда мы создаем какой-либо объект ядра Windows: файл, сокет, окно, мьютекс, то всегда получаем как результат не сам объект (не его адрес в памяти), а некоторое целое число, дескриптор (handle), которое однозначно характеризует системный ресурс. С одной стороны, дескриптор развязывает виртуальную память приложения и виртуальную память ядра операционной системы, а с другой стороны, выполняет функцию автоматического контроля существования объекта, так как любое обращение к объекту происходит через его дескриптор. Эту же технику мы можем применить в нашем случае для решения задачи корректного уничтожения потоков. Определим в первом приближении интерфейс объекта-арбитра, который будет разрешать все конфликты, связанные с созданием потоков, их уничтожением и получением доступа к потокам:

TMyThreadManager = class

public

function Add(aThread: TMyThread): Integer;

procedure Release(aHandle: Integer);

function Lock(aHandle: Integer): TMyThread;

end;

Предполагается, что все потоки порождаются от некоторого базового класса TMyThread. После конструирования объекта потока он передается арбитру-менеджеру, который сохраняет его в своих недрах и возвращает целочисленный дескриптор, однозначно характеризующий поток. Приложение (или другой поток-родитель) сохраняет у себя дескриптор созданного потока. Когда поток больше не нужен, то его можно уничтожить, вызывав операцию Release и передав ей дескриптор потока. Если поток уже был уничтожен, то ничего не произойдет, а если поток еще жив, то будет сделана попытка его уничтожения. Здесь важен акцент на слове "попытка", а не на слове "уничтожение", но об этом чуть позже. Когда поток приложения (или любой другой поток) желает взаимодействовать с потоком, дескриптор которого ему известен, он может блокировать поток в памяти, вызвав метод Lock, который возвращает действительный объект потока или nil, если потока больше не существует. Когда поток завершается самостоятельно или другим объектом, которому он больше не нужен, то это делается с помощью уже известного нам метода Release.