SynchroSpace.h

См. документацию.
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

SourceForge.net Logo
© Овсеевич Р.В. Документация по CntmLib 1.1.4 от 28 May 2008. Создано системой  doxygen 1.5.3