|
Программирование >> Многопоточная библиотека с принципом минимализма
public: ConcreteLifetimeTracker(T* p, unsigned int longevity, Destroyer d) :LifetimeTracker(longevity), pTracked (p), destroyer (d) {} -ConcreteLifetimeTrackerO { destroyer (pTracked ); private: T* pTracked ; Destroyer destroyer ; void AtExitFnO; необходимое объявление template <typename т, typename Destroyer> void SetLongevity(T* pDynObject, insigned int longevity, Destroyer d = Private::Deleter<T>::Delete) TrackerArray pNewArray = static cast<TrackerArray>( std::realloc(pTrackerArray, elements + 1)); if (IpNewArray) throw std::bad alloc(); LifetimeTracker* p = new ConcreteLifetimeTracker <T, Destroyer>(pDynObject, longevity, d); pTrackerArray = pNewArray; TrackerArray pos = std::upper bound( pTrackerArray, ртгаскегАггау + elements, longevity, compare); std::copy backward(pos, pTrackerArray + elements, PTrackerArray + elements + 1); *pos = p; ++elements; std::atexit(AtExitFn); Использование функций std: :upper bound и std::copy backward намного облегчает чтение и понимание этого нетривиального кода. Описанная выше функция вставляет вновь созданный указатель на объект класса ConcreteLifetimeTracker в упорядоченный массив, на который ссыпается указатель pTrackerArray, сохраняет порядок следования его элементов, а также обрабатывает ошибки и возникающие исключительные ситуации. Теперь цель функции LifetimeTracker::Compare проясняется. Массив, на который ссылается указатель pTrackerQueue, упорядочен в соответствии с продолжительностью жизни объектов. Объекты с наибольшей продолжительностью жизни находятся в начале массива. Объекты с одинаковой продолжительностью жизни следуют в соответствии с очередностью их вставки. Функция AtExitFn выталкивает объект с наименьшей продолжительностью жизни (т.е. последний элемент массива) и удаляет его. Удаление указателя на объект класса LifetimeTracker приводит к вызову деструктора класса ConcreteLifetimeTracker, который в свою очередь удаляет объект. static void AtExitFnO { assert(elements > О && pTrackerArray != 0); выбираем элемент, находящийся на вершине стека LifetimeTracker* ртор = pTrackerArray[elements - 1]; удаляем этот объект из стека. Ошибки не проверяются - функция realloc, примененная к меньшему размеру памяти, всегда работает правильно рТгаскегАггау = (TrackerArray*)std::геаПосСрТгаскегдггау, -elements); Уничтожаем элемент delete рТор; Создавая функцию AtExitFn, следует быть внимательным. Она должна выталкивать из стека элемент, находящийся на верщине, и удалять его. В свою очередь деструктор удаляемого элемента ликвидирует управляемый им объект. Проблема заключается в том, что функция AtExitFn должна вытолкнуть объект из верщины стека до его удаления, поскольку разрущение одного объекта может повлечь за собой создание другого, который будет затолкнут обратно в стек. Несмотря на то что это выглядит довольно необычно, именно так развиваются события, когда деструктор класса Keyboard пытается использовать объект класса Log. По умолчанию код скрывает структуры данных, и функция AtExitFn находится в пространстве имен Private. Пользователи могут любоваться лищь верщиной айсберга - функцией SetLongevi ty. Синглтоны с заданной продолжительностью жизни могут использовать функцию SetLongevi ty следующим образом. class Log public: static void CreateC) { Создаем экземпляр pinstance = new Log; Эти строки добавлены SetLongevity(*this, longevity.); Остальная часть реализации пропущена. функция Log::instance определяется, как и прежде, private: Определяем фиксированную продолжительность жизни static const unsigned int longevity. = 2; static Log* pinstance ; Если реализовать классы Keyboard и Display, следуя описанному выще подходу, но задать продолжительность жизни их объектов равной 1, то объект класса Log их обязательно переживет. Решает ли это проблему KDL? Что если приложение использует несколько потоков? 6.9. Продолжительность жизни объектов в многопоточной среде Синглтоны должны работать и с потоками тоже. Допустим, наше приложение только что начало работу, и два потока имеют доступ к приведенному ниже синглтону. Singleton* Singleton::instanceC) ifClpinstance) 1 pinstance = new singleton; 2 return *plnstance ; 3 Первый поток входит в функцию instance и проверяет условие оператора if. Поскольку поток входит в функцию впервые, значение переменной plnstance равно 0. В таком случае поток управления достигает строки с комментарием 2 и готовится вызвать оператор new. Планировщик заданий операционной системы может прервать первый поток в этой точке и передать управление другому потоку. Второй поток вызывает функцию Singleton::InstanceC) и обнаруживает, что значение переменной plnstance также равно нулю, поскольку первый поток ее еще не менял. До сих пор первый поток только проверял значение переменной plnstance . Теперь второй поток вызывает оператор new, присваивает переменной plnstance некий адрес и покидает функцию. К несчастью, первый поток снова приходит в сознание, вспоминает, что осталось выполнить лищь строку с комментарием 2, изменяет значение переменной plnstance и выходит из функции. Когда пыль рассеивается, оказывается, что вместо одного объекта класса Singleton созданы два, причем один из них очевидно лииший. В каждом потоке хранится по одному объекту класса Singleton, и приложение погружается в хаос. И это только одна из возможных ситуаций! А что будет, если к синглтону имеют доступ несколько потоков? (Представьте себе процесс отладки такой программы!) Опытные программисты, разрабатывающие многопоточные приложения, узнают здесь классическое состязание (race situation). Следует быть готовым к тому, что шаблон проектирования Singleton столкнется с потоками. Синглтон относится к глобальным ресурсам совместного использования и должен участвовать в состязании с другими объектами, решая проблемы нескольких потоков. 6.9.1. Шаблон блокировки с двойной проверкой Всестороннее обсуждение многопоточных синглтонов впервые было проведено Дугласом Шмидтом (Douglas Schmidt, 1996). В этой же статье было огшсано очень остроумное решение, названное шаблоном Double-Checked Locking (блокировка с двойной проверкой), предложенное Дугласом Шмидтом и Тимом Харрисоном (Tim Harrison). Очевидное решение сушествует, но выглядит непривлекательно. Singleton& Singleton::lnstanceC) mutex - объект мьютекса. мьютексом управляет объект класса Lock Lock guardCmutex ); if С!plnstance ) plnstance = new Singleton; return *plnstance ; Класс Lock - это классический обработчик мьютексов (детали описаны в приложении). Конструктор класса Lock захватывает мьютекс, а деструктор освобождает его. Пока мьютекс niutex захвачен, другие потоки, пытающиеся завладеть им, ожидают своей очереди.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |