|
Программирование >> Многопоточная библиотека с принципом минимализма
Большинство из перечисленных выше решений принято в предположении, что в классе Base определен виртуальный деструктор. Это лишний раз доказывает, насколько важно делать деструкторы полиморфных классов виртуальными. Если этим требованием пренебречь, удаление указателя типа Base, который на самом деле ссылается на объект производного класса, может привести к непредсказуемым результатам. В этом случае механизм распределения памяти в режиме отладки приведет к срабатыванию макросов assert, а в режиме NDEBUG просто вызовет крах программы. Каждый из нас согласится, что такое поведение можно считать непредсказуемым . Для того чтобы не заботиться об этом (и не проводить бессонные ночи, отлаживая профамму, если вы вдруг забудете о том, что сказано выше), в классе SmallObject определен виртуальный деструктор. Любой класс, производный от класса Base, наследует его виртуальный деструктор. Это приводит нас к реализации класса SmallObject. Во всем приложении нам нужен один-единственный объект класса SmaliObjAllocator. Этот объект должен быть правильно создан и правильно разрушен, что само по себе трудно. К счастью, библиотека Loki позволяет решить эту проблему с помощью шаблонного класса SingletonHolder, описанного в главе 6. (Конечно, отсылать вас к следующим главам досадно, но еще досаднее потерять возможность повторного использования кода.) Пока можно рассматривать класс SingletonHolder как механизм, позволяющий осуществл5Пъ управление единственным экземпляром класса. Если этот класс имеет имя X, шаблонный класс конкретизируется как Singleton<x>. Затем, для того чтобы получить доступ к единственному экземпляру этого класса, нужно вызвать функцию Single-ton<x>:: instance С). Шаблон проектирования Singleton (Одиночка) описан в книге Gamma etal. (1995). Использование класса SingletonHolder позволяет чрезвычайно просто реализовать класс SmallObject. typedef Singleton<5mallObjAllocator> MyAlloc; void* SmallObject::operator newCstd::size t size) return MyAlloc::lnstance().Allocate(size); void SmallObject::operator delete(void* p, std::size t size) MyAlloc::Instance().Deal1ocate(p, size); 4.8. Просто, сложно и снова просто Реализация класса SmallObject оказалась довольно простой. Однако на самом деле не все так просто - ведь остались нерешенными проблемы, связанные с многопо-точностью. Единственный объект класса Small Allocate г используется всеми экземплярами класса SmallObject. Если эти экземпляры принадлежат разным потокам, то объект класса SmaliObjAllocator придется распределять между ними. Как указано в приложении, в этом случае нужно предпринимать специальные меры. Кажется, нам придется пройтись по всем уровням нашей архитектуры, выделить критические операции и добавить соответствующую блокировку. Однако многопоточность не является неразрешимой проблемой, поскольку в библиотеке Loki уже определены механизмы синхронизации объектов высокого уровня. Следуя поговорке лучший способ повторного использования кода - его применение , включим заголовочный файл Threads.h из библиотеки Loki и внесем в класс SmallObject следующие изменения (они выделены полужирным шрифтом). template <temp1ate <c1ass T> class ThreadingMode1> class SmallObject : public ThreadingMode1<Smanobject> ... как и раньше ... Определения операторов new и delete также подвергаются пластической операции. emplate <template <class т> class ThreadingModel> void* smallObject<ThreadingModel>::operator new(std::size t size) Lock lock; return MyAlloc::instance().Allocate(size); emplate <template <class т> class ThreadingModel> void SmallObject<ThreadingModel>::operator delete(void* p, std::size t size) Lock lock; MyAlloc::Instance().DeallocateCp, size); Вот и все! Все нижележащие уровни нащей реализации изменять не нужно - их функциональные возможности уже защищены блокировкой на верхнем уровне. Синглтоны и механизмы многопоточности, предусмотренные в библиотеке Loki, свидетельствуют о могуществе повторного использования кода. Каждый из этих вопросов - время жизни глобальной переменной и многопоточность - по-своему сложен. Преодолеть эти сложности на основе первого способа реализации класса SmallObject слищком трудно - хотя бы потому, что в классе FixedAllocator приходится выполнять кэширование при инициализации одного и того же объекта для каждого потока в отдельности. 4.9. Применение в этом разделе показано, как использовать файл SmallObj .h в приложениях. Для того чтобы применить класс SmallObj, нужно задать соответствующие параметры конструктора класса Small Allocator: размер объекта класса Chunk и максимальный размер объекта, который может еще считаться небольшим. Что значит небольшой объект? Какие размеры считаются небольшими ? Чтобы ответить на эти вопросы, вернемся к предназначению механизма распределения памяти для небольших объектов. С его помощью мы хотели повысить эффективность использования памяти и понизить затраты времени, преодолев ограничения, наложенные стандартным механизмом. Размеры дополнительной памяти, которая используется стандартным распределителем, сильно варьируются. Как-никак, стандартный механизм распределения памяти тоже может применять стратегии, изложенные в этой главе. В большинстве случае, однако, следует ожидать, что размер вспомогательной памяти на обычных машинах будет изменяться от 4 до 32 байт для каждого объекта. Для распределителя памяти, который для каждого объекта резервирует 16 байт дополнительной памяти, непроизводительные затраты составят 25 %; таким образом, объект размером 64 байт можно считать небольшим . С другой стороны, если объект класса Small Allocator размещает в памяти большие объекты, в конце концов выделенной памяти окажется намного больше, чем нужно (не забывайте, что класс FixedAllocator стремится оставить в памяти хотя бы один объект класса Chunk, даже если все объекты удалены). Библиотека Loki предоставляет выбор. В файле SmallObj.h Определены три символа препроцессора, приведенные в табл. 4.L Все исходные файлы, входящие в проект, следует компилировать, указав один и тот же символ препроцессора (или не задавая его вообще). Если этого не сделать, ничего смертельного не случится - просто будет создано больще объектов FixedAllocator, предназначенных для разных размеров. Параметры, предусмотренные по умолчанию, предназначены для мащин с разумным объемом физической памяти. Если в директиве #define символы MAX SMALL 0b3ect size ИЛИ DEFAULT CHUNK SIZE определить равными НуЛЮ, ТО В заголовочном файле SmallObj.h будет применяться условная компиляция, которая просто использует обычные операторы ::operator new и ::operator delete, не прибегая к выделению вспомогательной памяти вообще. Интерфейс объектов остается прежним, но их функции становятся подставляемыми заглущками (inline stubs), что приводит к стандартному механизму распределения динамической памяти. Обычно шаблонный класс SmallObject имеет только один параметр. Для поддержки разных размеров участков памяти и небольших объектов этот класс получает еще два шаблонных параметра. По умолчанию ими являются константы de- FAULT CHUNK SIZE И MAX SMALL 0b3ECT SIZE соответственно. template < template <class T> class ThreadingModel = default threading, std::size t chunksize = default chunk size, std::size t maxSmallobjectSize = nax small ob]ect size > class SmallObject; Если просто написать SmallObjo, вы получите класс, который может работать со стандартной моделью потоков. Таблица 4.1. Символы препроцессора, использованные в файле SmallObj.h
4.10. Резюме в некоторых идиомах языка С++ очень широко применяются небольшие объекты, расположенные в динамической памяти. Это происходит благодаря тому, что в языке С++ динамический полиморфизм (runtime polymoфhism) тесно связан с распределением динамической памяти и семантикой указателей и ссылок. Однако стандартные
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |