Некоторые разработчики всесторонне используют многопоточные программные технологии и способны написать удивительно изощренный программный продукт с использованием примитивов синхронизации потоков, доступных из операционной системы. Другие разработчики больше сконцентрированы на решении вопросов, специфических для их области, и не утруждают себя написанием нудного потоко-безопасного кода. Третьи разработчики имеют особые ограничения по организации потоков, связанные с тем, что многие системы с управлением окнами (включая Windows) имеют очень строгие правила взаимодействия потоков и оконных примитивов. Еще один класс разработчиков может широко использовать старую библиотеку классов, которая неприязненно относится к потокам и не может допустить какого бы то ни было многопоточного обращения. У всех четырех типов разработчиков должна быть возможность использования объектов друг друга без перестраивания их потоковой стратегии, чтобы приспособиться ко всем возможным сценариям. Чтобы облегчить прозрачное использование объекта безотносительно к его осведомленности о потоках, СОМ рассматривает ограничения на параллелизм объектов как еще одну деталь реализации, о которой клиенту не нужно беспокоиться. Чтобы освободить клиента от ограничений параллелизма и реентерабельности (повторной входимости), в СОМ имеется весьма формальная абстракция, которая моделирует связь объектов с процессами и потоками. Эта абстракция носит название апартамент ( apartment ) [1] . Апартаменты определяют логическое группирование объектов, имеющих общий набор ограничений по параллелизму и реентерабельности. Каждый объект СОМ принадлежит к ровно одному апартаменту: один апартамент, однако, может быть общим для многих объектов. Апартамент, к которому относится объект, безоговорочно является частью идентификационной уникальности объекта.
Апартамент не является ни процессом, ни потоком; но в то же время апартаменты обладают некоторыми свойствами обоих этих понятий. Каждый процесс, использующий СОМ, имеет один апартамент или более; тем не менее, апартамент содержится ровно в одном процессе. Это означает, что каждый процесс, использующий СОМ, имеет как минимум одну группу объектов, которая удовлетворяет требованиям параллелизма и реентерабельности; однако два объекта, находящихся в одном и том же процессе, могут принадлежать к двум различным апартаментам и поэтому иметь разные ограничения по параллелизму и реентерабельности. Этот принцип позволяет библиотекам с совершенно различными понятиями о потоках мирно соучаствовать и одном общем процессе.
Поток одновременно выполняется в одном и только одном апартаменте. Для того чтобы поток мог использовать СОМ, он должен сначала войти в апартамент. Когда поток входит в апартамент, СОМ размещает информацию об апартаменте в локальной записи потока ( TLS – thread local storage ), и эта информация связана с потоком до тех пор, пока поток не покинет апартамент. СОМ обеспечивает доступ к объектам только для тех потоков, которые выполняются в апартаменте данного объекта. Это означает, что если поток выполняется в том же процессе, что и объект, то потоку может быть запрещено обращаться к объекту, даже если память, которую занимает объект, полностью видима и доступна. В СОМ определен HRESULT ( RPC_E_WRONG_THREAD ), который возвращают некоторые системные объекты при прямом обращении из других апартаментов. Объекты, определенные пользователем, также могут возвращать этот HRESULT ; однако лишь немногие разработчики желают пройти столь долгий путь для обеспечения правильного использования своих объектов.
В версии СОМ под Windows NT 4.0 определяется два типа апартаментов: многопоточные апартаменты ( МТА – multithreaded apartments ) и однопоточные апартаменты ( STA – singlethreaded apartments ). В каждом процессе может быть не больше одного МТА ; однако процесс может содержать несколько STA . Как следует из этих названий, в МТА может выполняться одновременно несколько потоков, а в STA – только один поток. Точнее, только один поток может когда-либо выполняться в данном STA ; что означает не только то, что к объектам, находящимся в STA , никогда нельзя будет обратиться одновременно, но также и то, что только один определенный поток может когда-либо выполнять методы объекта. Эта привязка ( affinity ) к потоку позволяет разработчикам объекта надежно сохранять промежуточное состояние между вызовами метода в локальной памяти потока TLS ( thread local storage ), а также сохранять блокировки ( locks ), требующие поточной привязки (например, критические секции Win32 и семафоры ( mutexes )).
Подобная практика приведет к катастрофе, если применять ее в случае объектов из МТА , так как в этом случае неизвестно, какой поток будет выполнять данный вызов метода. Неудобство STA заключается в том, что он позволяет одновременно выполняться только одному вызову метода, независимо от того, сколько объектов принадлежат к данному апартаменту. В случае МТА потоки могут быть распределены динамически на основании текущих запросов, вне зависимости от количества объектов в апартаменте. Для того чтобы создать параллельные серверные процессы с использованием только однопоточных апартаментов, потребуется много апартаментов, и если не соблюдать осторожность, то может возникнуть непомерное количество потоков. Кроме того, уровень параллелизма в основанных на STA обслуживающих процессах не может превышать общее число объектов в процессе. Если процесс сервера содержит только малое число крупномодульных ( coarse-grained ) объектов, то удастся обойтись малым числом потоков, даже если каждый объект существует в своем отдельном STA .
В будущей реализации СОМ будет, возможно, введен третий тип апартамента – апартамент, арендованный потоками ( RTA – rentalthreaded apartment ). Подобно МТА , RTA позволяет входить в апартамент более чем одному потоку. Но, в отличие от МТА , когда поток входит в RTA , он вызывает блокировку всего апартамента ( apartment-wide lock ) (то есть он как бы арендует апартамент), которая запрещает другим потокам одновременно входить в апартамент. Эта блокировка апартамента снимается, когда поток покидает RTA , что позволяет войти следующему потоку. В этом отношении RTA подобен МТА , за исключением того, что все вызовы методов осуществляются последовательно. Это обстоятельство делает RTA значительно удобнее для классов, относительно которых неизвестно, являются ли они потокобезопасными. Хотя все вызовы в STA также осуществляются сериями, объекты на основе RTA отличаются тем, что в них нет привязки потоков; то есть внутри RTA могут выполняться любые потоки, а не только тот исходный поток, который создал апартамент. Эта свобода от привязки к потоку делает объекты на базе RTA более гибкими и эффективными, чем объекты на основе STA , так как любой поток потенциально может сделать вызов объекта, просто войдя в RTA объекта. На момент написания этого текста еще окончательно не определились детали создания апартаментов RTA и входа в них. За подробностями вы можете обратиться к документации по SDK .
Когда поток впервые создается операционной системой как результат вызова CreateProcess или CreateThread , этому новообразованному потоку не сопоставлен ни один апартамент. Перед тем как использовать СОМ, новый поток должен войти в какой-либо апартамент путем вызова одной из приведенных далее API-функций.
В Windows NT 5.0 это будет изменено. За подробностями обращайтесь к документации по SDK.
HRESULT CoinitializeEx(void *pvReserved, DWORD dwFlags);
HRESULT Coinitialize(void *pvReserved);
HRESULT OleInitialize(vo1d *pvReserved);
Для всех трех только что описанных API-функций первый параметр зарезервирован и должен равняться нулю.
CoInitializeEx является API-функцией самого низкого уровня и позволяет вызывающему объекту определять, в какой тип апартамента нужно войти. Для того чтобы войти в МТА всего процесса, вызывающий объект должен использовать флаг COINIT_MULTITHREADED:
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
Для входа во вновь соаданный STA вызывающий объект должен выставить флаг COINIT_APARTMENTTHREADED :
HRESULT hr = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
Каждый поток в процессе, который вызывает CoInitializeEx с применением COINIT_MULTITHREADED , выполняется в том же самом апартаменте. Каждый поток, который вызывает CoInitiаlizeEx с применением COINIT_APARTMENTTHREADED , выполняется в отдельном апартаменте, в который не могут входить никакие другие потоки. CoInitialize – это традиционная подпрограмма, которая просто вызывает CoInitializeEx с флагом COINIT_APARTMENTTHREADED . Olelnitialize сначала вызывает CoInitialize , а затем инициализирует несколько подсистем из OLE-приложений, таких как OLE Drag and Drop и OLE Clipboard . Если не предполагается использовать эти службы более высокого уровня, то в общем случае предпочтительнее использовать CoInitialize или CoInitializeEx .
Каждая из этих трех API-функций может быть вызвана больше одного раза на поток. Первый вызов каждого потока возвратит S_ОК . Последующие вызовы просто повторно войдут в тот же апартамент и возвратят S_FALSE . На каждый успешный вызов CoInitialize или CoInitializeEx из того же потока нужно вызвать CoUninitialize . На каждый успешный вызов OleInitialize из того же потока нужно вызвать OleUninitialize . Эти подпрограммы деинициализации ( uninitialization ) имеют очень простые сигнатуры:
void CoUninitialize(void);
void OleUninitialize(void);
Если все эти подпрограммы перед прекращением потока или процесса не вызваны, это может задержать восстановление ресурсов. Когда поток входит в апартамент, недопустимо изменять типы апартамента с использованием CoInitiаlizeEx . При попытках сделать это HRESULT будет содержать RPC_E_CHANGED_MODE . Однако, если поток полностью покинул апартамент посредством CoUninitialize , он может войти в другой апартамент путем повторного вызова CoInitializeEx . |