Добавить в цитаты Настройки чтения

Страница 188 из 372

MSG msg;

while(GetMessage(&msg, 0, 0, 0))

             DispatchMessage(&msg);

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

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

Апартаменты

СОМ появился до того, как в Windows появилась многопоточность. В связи с этим, в рамках одного приложения могут взаимодействовать объекты, имеющие различный уровень знаний о потоках. Некоторые объекты ничего не знают о потоках. Если их погрузить в среду, в которой выполняется несколько потоков, то возможны проблемы. При параллельном вызове одного и того же метода такого объекта возможны, например, ранее описанные ошибки модификации глобальных и статических переменных. Другие объекты могут быть написаны с учетом возможности параллельного вызова их методов, например, с использованием критических секций. Такие объекты называются потоко-безопасными.

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

Апартамент — это относительно сложное понятие. Но, тем не менее, следует затратить определенные усилия на то, чтобы разобраться в данном вопросе. Имеются экспертные оценки, в соответствии с которыми до 40 процентов ошибок при создании систем, основанных на технологии СОМ, вызваны недостаточным пониманием апартаментов.

Итак, начнем с иерархии процессов, апартаментов, потоков и объектов:

• В каждом процессе имеется один или несколько апартаментов.

• Каждый поток живет в определенном апартаменте.

• Каждый объект живет в определенном апартаменте.

Имеется три типа апартаментов:

 STA (Single-Threaded Apartment)

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

• МТА (MultiThreaded Apartment)

В одном процессе может иметься только один МТА. В МТА живут объекты, поддерживающие параллельный вызов своих методов. С МТА связан пул потоков (рабочих потоков), из которого и выбирается новый поток для выполнения нового вызова метода объекта, живущего в данном апартаменте.

• NA (Neutral apartment)

В одном процессе может иметься только один NA. В NA живут объекты, поддерживающие параллельный вызов своих методов. Нет потоков, связанных с NA навечно. Любой поток из STA или МТА может временно покинуть свой апартамент и заняться выполнением некоторого метода некоторого объекта, живущего в NA.

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

Потоки создаются клиентом. Клиент имеет возможность связать данный поток с STA или МТА апартаментами. При этом либо создается новый апартамент, либо поток связывается с уже существующим апартаментом.





Если поток собирается работать с СОМ, он должен вызвать

CoInitialize(NULL), CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)

или

CoInitializeEx(NULL, COINIT_MULTITHREADED).

В первых двух случаях создается НОВЫЙ STA и данный поток связывается с этим STA. Первый из созданных STA объявляется главным. В третьем случае создается МТА, если он не был создан ранее. Данный поток связывается с МТА.

При создании апартамента в куче создается специальный объект апартамента, содержащий, в частности, такую информацию как идентификатор апартамента и его тип. Связь потока с апартаментом обеспечивается тем, что идентификатор апартамента сохраняется в локальной памяти потока (TLS — Thread Local Storage).

Создание объекта инициируется в определенном потоке, который уже приписан некоторому апартаменту. Но тип объекта, допустимость параллельного вызова его методов не определяется при создании объекта. Эта информация задается разработчиком класса и хранится в реестре системы.

Мы уже знаем, что если некоторый сервер предназначен для выполнения в процессе клиента, то для каждого его класса в реестре системы имеется раздел InProcServer32. В этом разделе имеется параметр по умолчанию, значением которого является путь к DLL, содержащей код данного сервера. В этом же разделе может иметься именованный параметр — ThreadingModel. Именно этот параметр определяет тип апартамента, в котором может жить экземпляр данного класса. В таблице 2.1 перечислены возможные значения данного параметра и допустимые типы апартаментов.

Table: Соответствие потоковой модели класса и допустимого типа апартамента

Потоковая модель ∙ Тип апартамента

Нет ∙ Главный STA

Apartment ∙ Любой STA

Free ∙ МТА

Both ∙ Любой STA или МТА

Neutral ∙ NA

Отметим несколько моментов, связанных с этой таблицей.

Значение Нет соответствует случаю класса, который создавался до внедрения многопоточности в Windows. Все экземпляры таких классов помещаются в главный STA. В связи с тем, что с этим апартаментом связан только один поток, методы всех объектов, живущих в данном апартаменте, не могут выполняться параллельно. Это гарантирует безопасность работы с такими объектами в многопотоковой среде. Но эта безопасность достигается за счет неизбежного снижения производительности системы. Чем больше объектов живет в главном STA, тем дольше приходится клиенту ожидать выполнения вызова метода живущего в этом апартаменте объекта.

Значение Apartment означает, что экземпляр данного класса не рассчитывает на параллельный вызов его методов, хотя сам обращается к глобальным переменным (например, счетчикам числа активированных объектов) безопасным образом. Объкт, поддерживающий данную модель, будет размещен в STA. Если создавший данный объект поток сам живет в STA, то именно в этом STA и будет жить новый объект. Если же этот поток принадлежит МТА, то будет создан новый STA (и новый связанный с ним поток), в который и будет помещен созданный объект. Данная модель позволяет параллельно обращаться к объектам, живущим в различных STA. Это существенное улучшение по сравнению с использованием одного главного STA.

Значение Free означает, что экземпляр данного класса знает о потоках все и готов к получению параллельных вызовов своих методов. Такой объект помещается в МТА (МТА создается, если его ранее не было).

Значение Both означает, что экземпляр данного класса не только готов к получению параллельных вызовов своих методов, но и осторожен в обращении со своими клиентами. При взаимодействии двух объектов разделение их по признаку клиент/сервер достаточно условно. Вызываемый объект в свою очередь может вызвать какие-то методы вызывающего объекта. Разделяя потоко-безопасные и потоко-опасные объекты по различным апартаментам (МТА и STA) мы обеспечиваем, кроме всего прочего, защиту вызывающего объекта от вызываемого.

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