00001 /* 00002 * CntmLib - Подсчет ссылок, потоки, синхронизация, асинхронные процедуры, события 00003 * Copyright (c) 2005, Овсеевич Роман, CntmLib@mail.ru 00004 * _______________________________________________________________________________ 00005 * Разрешено свободное использование, копирование, распространение, изменение 00006 * (изменение сведений об авторских правах запрещено). Запрещена продажа и 00007 * включение всей библиотеки или ее частей в другие библиотеки. В сведениях об 00008 * авторских правах на программу (или сведениях о программе, об авторах, 00009 * использованных средствах разработки и т.д.) должна быть указана информация 00010 * о библиотеке CntmLib, ее авторе и, возможно, сайте или email'е. 00011 * Библиотека поставляется "как есть", без каких-либо гарантий со стороны автора. 00012 */ 00013 00014 #ifndef CNTM_SYNCHROSPACE_H 00015 #define CNTM_SYNCHROSPACE_H 00016 #include <boost/utility.hpp> 00017 #include <Cntm/SystemUtils/SyncUtils.h> 00018 #include <Cntm/SystemUtils/SysThreadIdentifier.h> 00019 #include <Cntm/SystemUtils/SysThreadLocalInt.h> 00020 #include <Cntm/Exceptions/NullArgException.h> 00021 #include <Cntm/Utils/LinkedListItemBase.h> 00022 #include <Cntm/RefCount/RefBase.h> 00023 #include <Cntm/Synchro/IBasicSynchro.h> 00024 00025 namespace Cntm 00026 { 00027 00028 /** 00029 * Класс Cntm::SynchroSpace - базовый класс для синхропространств. 00030 * 00031 * По своей сути синхропространство является критической секцией (мьютексом), синхронизирующей операции объектов, принадлежащих синхропространству (синхрообъектов, классы которых унаследованы от Cntm::Synchro...Base). Для того, чтобы при выполнении операции объекта в многопоточной среде не происходило гонок, такие операции должны выполняться внутри синхропространства, для чего поток выполнения должен войти в синхропространство. В каждый момент времени в синхропространстве может находится только один поток выполнения. 00032 * 00033 * Существует 2 режима входа входа в пространство: реентерабельный и не реентерабельный. Сначала рассмотрим что такое реентерабельный (повторный) вход на примере оконной системы. Пусть у нас есть форма, есть периодический таймер и есть обаботчик сигнала от этого таймера. Пусть в этом обработчике мы отображаем модальное окно сообщения. Тогда если долго его не закрывать сработает таймер и снова будет вызван обработчик. Получится, что второй вызов произошел рекурсивно из той точки обработчика, где произведено отображение диалогового окна. В большинстве случаев это не страшно, однако в сложных системах может приводить к трудноуловимым ошибкам. Рекурсивным может быть вход не только в функцию, но и в объект, например, когда из метода, не закончившего до конца изменять данные, каким-то косвенным образом вызывается другой метод того же объекта, изменяющего те же самые данные, что может привести к ошибке. Для предотвращения таких ошибок следует использовать нереентерабельный режим входа в синхропространство, который блокирует возможность косвенного повторного входа в синхропространство. Конкретные аспекты будут рассмотрены для каждого типа синхропространств отдельно. Использование реентерабельного режима рекомендуется свести к минимуму. 00034 * 00035 * Другое назначение синхропространства - выполнение отложенных синхрозаданий. Использование и выполнение этих заданий выглядит следующим образом. Поток выполнения создает задание и добавляет его в очередь (FIFO) заданий указанного синхропространства (при этом не важно произведен ли вход в указанное синхропространство или нет). После этого механизм синхропространства производит вход в это пространство (как только это будет возможно) в реентерабельном режиме (см. выше) и выполненяет задния из очереди, т.е. выполнение каждого задания начинается в реентерабельном режиме. Если добавление задания в синхропространство производится внутри этого пространства, то гарантируется, что выполнение заданий начнется только после выхода из этого синхропространства, т.е. при добавления заданий реентерабельный вход в пространство невозможен. При реентерабельном входе в синхропространство (например, при отображении модального окна) произойдет выполнение отложенных заданий. 00036 * 00037 * Через механизм отложенных синхрозаданий работают такие компоненты системы как деинициализация синхрообъектов и отложенные события и процедуры. 00038 * 00039 * Синхропространства можно поделить на главные (Cntm::...MainSynchroSpace) и дополнительные (Cntm::ExtraSynchroSpace). В системе может быть только одно главное синхропространство (на всем протяжении работы системы) и несколько дополнительных. Задачи дополнительных синхропространств описаны выше, задачи главного синхропространства несколько шире. К задачам главного синхропространства относится обеспечение жизненного цикла приложения. Под этим подразумевается, что оно запускает цикл обработки сообщений в главном потоке (или использует циклы других систем, например QT). Система работает, пока работает цикл, система переходит к завершению работы, когда произведен выход из цикла (пример: в QT запуск цикла производится методом QApplication::exec() - a.exec(), после выхода из которого система приступает к завершению работы). Важной особенностью главного синхропространства является то, что оно препятствует завершению работы, пока остается хотя бы один синхрообъект или одно синхропространство. Это повышает надежность завершающих операций, одновременно требует более четкой проектировки структуры объектов. Конкретное описание различных типов главных синхропространств см. в описании соответствующих классов Cntm::...MainSynchroSpace. 00040 * 00041 * Замечания для случая нескольких синхропространств. Несколько синхропространств необходимо когда в системе выполняются длительные операции (например работа с пользовательским интерфейсом) и одновременно требуется быстрая реакция на какие-то события. В это случае следует разделить синхропространства на то, в котором будет производится медленная работа с пользовательским интерфейсом и дополнительное, которое будет обрабатывать события, требующие быстрой реакции. Не стоит без необходимости создавать лишние синхропространства. Когда имеется несколько пространств возможен вложенный вход, т.е. сначала произведен вход в одно пространство, а из него - в другое. Т.к. синхропространства по своей сути являются критическими секциями, то возможен случай смертильных объятий, когда один поток выполнения вошел в 1 пространство и ожидает входа во 2, а другой поток выполнения наоборот. Что бы этого избежать следует входить в синхропространства всегда в одном и том же порядке, другими словами, если составить граф, в котором вершинами будут синхропространства, а направленными дугами - порядок входа из одного пространства в другое, то такой граф не должен содержать циклов. 00042 * 00043 * В заключение следует сказать, что методы синхропространства редко используются напрямую, обычно все операции выполняются через синхрообъекты. 00044 * 00045 * Данный класс обеспечивает многопоточность. 00046 * @author Овсеевич Р. 00047 * \ingroup Synchro 00048 */ 00049 class SynchroSpace: public RefBase, virtual public IBasicSynchro, boost::noncopyable 00050 { 00051 protected: 00052 00053 class TaskQueue; 00054 00055 public: 00056 00057 typedef RefPtr<SynchroSpace> Ptr; 00058 00059 /** 00060 * Базовый класс синхронной задачи. Система гарантирует, что методы задачи будут вызваны синхронно в том синхропространстве, куда она будет добавлена. 00061 * 00062 * Выполнение задачи происходит отложенно от постановки задачи в очередь (т.е. если постановка задачи произошла внутри синхропространства, то выполнение начнется только после выхода из него) внутри синхропространства с реентерабельным режимом входа. Если в процессе выполнения задачи произойдет реентерабельный вход в механизм выполнения задач, то выполнение задач начнется с начала очереди. 00063 * @author Овсеевич Р. 00064 */ 00065 class TaskBase: LinkedListItemBase 00066 { 00067 public: 00068 00069 /** 00070 * Вуртуальный деструктор. 00071 */ 00072 virtual ~TaskBase() {} 00073 00074 /** 00075 * Выполнить задачу. Если задача рекурсивная (IsRecurse() = true), то возможен повторный вход в данный метод (если при выполнении данного метода произойдет запуск вложенного цикла обработки задач). 00076 * 00077 * Исключения, сгенерированные данным методом, игнорируются системой. 00078 * @param Recursive - флаг, сообщающий какой это вызов: первый (false) или рекурсивный (true). 00079 */ 00080 virtual void Exec(bool Recursive = false) =0; 00081 00082 /** 00083 * Характеристика задачи, указывающая является ли задача рекурсивной или нет (постоянна на всем протяжении жизни объекта). Если возвращается false, то рекурсивных вызовов метода Exec() никогда не будет. Если возвращается true, то при запуске вложенного цикла обработки задач из данного метода опять будет вызван метод Exec() этого объекта (рекурсивно). 00084 */ 00085 virtual bool IsRecursive() const = 0; 00086 00087 private: 00088 00089 friend class TaskQueue; 00090 }; 00091 00092 00093 /** 00094 * Возвращает ссылочный указатель на объект главного синхропространства или NULL, если оно отсутствует. 00095 */ 00096 static Ptr Main() { return mainFactory? mainFactory->QueryMainInstance() : Ptr(); } 00097 00098 /** 00099 * Возвращает ссылочный указатель на себя. 00100 */ 00101 SynchroSpace::Ptr Space() const { return this; } 00102 00103 /** 00104 * Возвращает текущий режим входа в синхропространство. Возвращает true, если вход во !все! синхропространства, в которых поток выполнения находится в данный момент, был произведен в реентерабельном режиме. Если был хотя бы один вход в нереентерабельном режиме, то возвращается false. 00105 * 00106 * Метод должен вызываться только после входа в синхропространство и в том же потоке, который произвел вход. 00107 */ 00108 bool IsReentrantMode() const; 00109 00110 /** 00111 * Производит вход в синхропространство. Если синхропространство занято другим потоком, то производит ожидание, пока другой поток не выйдет из синхропространства. 00112 * 00113 * Допустим рекурсивный (повторный) вход из потока выполнения который уже находится в этом синхропространстве (возможно с другим режимом входа). 00114 * 00115 * Более подробное описание режимов входа и случаев, когда вход в синхропространство выполняется из другого синхропространства приведенов в описании класса Cntm::SynchroSpace. 00116 * 00117 * Вместо вызова метода Enter() и парного ему метода Leave() рекомендуется использовать классы Cntm::Sync и Cntm::ReentrantSync, которые обеспечивают более надежный и удобный способ вызова этих методов. Объекты этих классы производят вход в синхропространство от момента их создания до конца блока, в котором они объявлены. 00118 * @param ReentrantMode - режим входа (реентерабельный или нет). Описание режимов входа приведено в описании класса Cntm::SynchroSpace. 00119 */ 00120 void Enter(bool ReentrantMode = false); 00121 00122 /** 00123 * Производит попытку входа в синхропространство. Если синхропространство занято другим потоком, то возвращает false, иначе входит в синхропространство и возвращает true. 00124 * 00125 * Допустим рекурсивный (повторный) вход из потока выполнения который уже находится в этом синхропространстве (возможно с другим режимом входа). 00126 * 00127 * Более подробное описание режимов входа и случаев, когда вход в синхропространство выполняется из другого синхропространства приведенов в описании класса Cntm::SynchroSpace. 00128 * 00129 * Вместо вызова метода TryEnter() и парного ему метода Leave() рекомендуется использовать классы Cntm::TrySync и Cntm::TryReentrantSync, которые обеспечивают более надежный и удобный способ вызова этих методов. Объекты этих классы производят вход в синхропространство от момента их создания до конца блока, в котором они объявлены. 00130 * @param ReentrantMode - режим входа (реентерабельный или нет). Описание режимов входа приведено в описании класса Cntm::SynchroSpace. 00131 */ 00132 bool TryEnter(bool ReentrantMode = false); 00133 00134 /** 00135 * Производит выход из синхропространства. Парный метод для Enter() и TryEnter() (когда последний вернул true). Параметр ReentrantMode должен иметь такое же значение, как и у парного ему метода Enter() и TryEnter(). 00136 * 00137 * Более подробное описание режимов входа и случаев, когда вход в синхропространство выполняется из другого синхропространства приведенов в описании класса Cntm::SynchroSpace. 00138 * @param ReentrantMode - режим входа (реентерабельный или нет). Описание режимов входа приведено в описании класса Cntm::SynchroSpace. 00139 */ 00140 void Leave(bool ReentrantMode = false); 00141 00142 /** 00143 * Добавить новую задачу в конец очереди. Объект задачи д.б. создан в динамической памяти. После выполнения задача автоматически будет удалена. Класс объекта задачи д.б. унаследован от Cntm:: SynchroSpace:: TaskBase. Если в процессе добавления задачи возникнет исключение, задача будет автоматически удалена. 00144 * 00145 * Подробнее о синхрозадачах см. описание класса Cntm::SynchroSpace и Cntm::SynchroSpace::TaskBase. 00146 * 00147 * Исключение: NullArgException если не указана задача. 00148 * @param Task - задача, которую нужно выполнить. Объект задачи д.б. создан в динамической памяти. 00149 */ 00150 virtual void AddSynchroTask(TaskBase* Task) = 0; 00151 00152 protected: 00153 00154 /** 00155 * Виртуальный деструктор. 00156 */ 00157 virtual ~SynchroSpace() {} 00158 00159 /** 00160 * Класс очереди задач для синхронного выполнения. Обеспечивает операции добавления задач и их синхронного выполнения. Перед добавлением необходимо создать объект (класс объекта д.б. унаследован от Cntm:: SynchroSpace:: TaskBase) в динамической памяти. После выполнения данный объект будет автоматически уничтожен очередью. 00161 * 00162 * Данный класс является частично многопоточным. Вызовы Add синхронизированы друг с другом и с вызовом Exec(), вызовы Exec() друг с другом не синхронизированы, но м.б. рекурсивны. 00163 * @author Овсеевич Р. 00164 */ 00165 class TaskQueue: boost::noncopyable 00166 { 00167 public: 00168 00169 /** 00170 * Конструктор. 00171 */ 00172 TaskQueue(): lastRecurseTask(TasksHead()), needExec(true) {} 00173 00174 /** 00175 * Добавить новую задачу в конец очереди. Объект задачи д.б. создан в динамической памяти. После выполнения задача автоматически будет удалена. Класс объекта задачи д.б. унаследован от Cntm:: SynchroSpace:: TaskBase. 00176 * 00177 * Метод возвращает true, если на момент добавления очередь была пуста. Это значит, что после добавления необходимо запустить выполнение задач очереди. В противном случае возвращается false. Это означает, что запрос на выполнение очереди уже был поставлен ранее или уже идет выполнение. 00178 * 00179 * Исключение: NullArgException если не указана задача. 00180 * @param Task - задача, которую нужно выполнить. Объект задачи д.б. создан в динамической памяти. 00181 */ 00182 bool Add(TaskBase* Task); 00183 00184 /** 00185 * Запустить задачи, находящиеся в очереди, на выполнение. После выполнения задача удаляется из очереди. Если при выполнении задачи происходит исключение, то оно игнорируется. 00186 */ 00187 void Exec() throw(); 00188 00189 private: 00190 00191 /** 00192 * Общая для всех очередей (статическая ) критическая секция, защищающая работу со списком задач. 00193 */ 00194 static SpecUtils::FastMutex mutex; 00195 00196 /** 00197 * Голова (фиктивный эл-т) списка задач. 00198 */ 00199 LinkedListItemBase tasksHead; 00200 00201 /** 00202 * Последняя рекурсивно выполняемая задача. Если выполнения нет (в т.ч. и после создания) указывает на голову (фиктивный эл-т) списка задач. 00203 */ 00204 TaskBase* lastRecurseTask; 00205 00206 /** 00207 * Флаг, требуется ли выполнение при добавлении задачи или нет. 00208 */ 00209 bool needExec; 00210 00211 /** 00212 * Возвращает указатель на голову (фиктивный эл-т) списка задач, приведенный к TaskBase*. 00213 */ 00214 TaskBase* TasksHead() { return static_cast<TaskBase*>(&tasksHead); } 00215 00216 /** 00217 * Возвращает слудующую задачу. 00218 */ 00219 TaskBase* NextTask(TaskBase* Task) 00220 { 00221 return static_cast<TaskBase*>(Task->NextItem()); 00222 } 00223 }; 00224 00225 00226 /** 00227 * Интерфейс для фабрики класса, возвращающей указатель на объект главного синхропространства. 00228 * 00229 * Фабрика должна быть зарегистрирована с помощью метода SetMainSynchroSpaceFactory. На всем протяжении программы допускается только одна фабрика, перерегистрация запрещена. 00230 */ 00231 class IMainSynchroSpaceFactory 00232 { 00233 public: 00234 virtual ~IMainSynchroSpaceFactory(){} 00235 00236 /** 00237 * Метод должен возвращать указатель на главное синхропространство или NULL, если такового не имеется. 00238 */ 00239 virtual SynchroSpace::Ptr QueryMainInstance() = 0; 00240 }; 00241 00242 00243 /** 00244 * Установить фабрику для получения указателя на объект главного синхропространства. В системе может быть только одно главное синхропространство, и если оно уже установлено, то возвращается false. Метод должен вызываться из главного потока. 00245 */ 00246 static bool SetMainSynchroSpaceFactory(IMainSynchroSpaceFactory* Factory) 00247 { 00248 if (mainFactory) return false; 00249 mainFactory = Factory; // Сохраняем фабрику. 00250 mainThreadId.Store(); // Запоминаем главный поток. 00251 return true; 00252 } 00253 00254 /** 00255 * Возвращает true, если метод был вызван из главного потока (потока, который установил фабрику главного синхропространства) и false - в противном случае. 00256 */ 00257 static bool IsMainThread() { return mainThreadId.Check(); } 00258 00259 /** 00260 * Выполнить вход в критическую секцию, связанную с синхропространством. 00261 */ 00262 virtual void DoEnter() = 0; 00263 00264 /** 00265 * Выполнить попытку входа в критическую секцию, связанную с синхропространством. 00266 */ 00267 virtual bool DoTryEnter() = 0; 00268 00269 /** 00270 * Выполнить выход из критической секции, связанную с синхропространством. 00271 */ 00272 virtual void DoLeave() = 0; 00273 00274 private: 00275 00276 /** 00277 * Интерфейс для получения указателя на объект главного синхропространства. Он д.б. зарегистрирован при инициализации главного синхропространства. 00278 */ 00279 static IMainSynchroSpaceFactory* mainFactory; 00280 00281 /** 00282 * Идентификатор главного потока (потока, котрый зарегистрировал фабрику главного синхропространства). 00283 */ 00284 static SpecUtils::SysThreadIdentifier mainThreadId; 00285 00286 /** 00287 * Счетчик реентерабельных входов, произведенных для конкретного потока выполнения. 00288 */ 00289 static SpecUtils::SysThreadLocalInt reentrantCounter; 00290 00291 /** 00292 * Счетчик нереентерабельных входов, произведенных для конкретного потока выполнения. 00293 */ 00294 static SpecUtils::SysThreadLocalInt noReentrantCounter; 00295 00296 /** 00297 * Изменить значения счетчиков входа при входе и выходе из синхропространства. 00298 */ 00299 void UpdateCounters(bool ReentrantMode, int Sign); 00300 }; 00301 00302 } 00303 00304 #endif //CNTM_SYNCHROSPACE_H
© Овсеевич Р.В. Документация по CntmLib 1.1.4 от 28 May 2008. Создано системой 1.5.3 |