А. Александреску - Современное проектирование на C++ (1119444), страница 41
Текст из файла (страница 41)
После повторной активизации первый поток также входит в синхронизированную часть, но слишком поздно — снова создаются два обьекта класса 5з пд) етоп. Кажется, что эта головоломка не имеет решения, но оказывается, что существует очень простое и элегантное решение. Оно называется шаблоном оооо)е-спескед ьоскз пд (блокировка с двойной проверкой).
Идея проста: проверяем условие, входим в синхронизированный код, а затем проверяем это условие снова. Указатель оказывается либо уже проинициализированным, либо нулевым. Ниже приведен код, позволяющий разобраться,и оценить преимущества шаблона блокировки с двойной проверкой. В нем действительно проявляется красота программирования. 51 од) етопе 54 пд) етоп:: 1пзтапсе 0 ( зФ (! ртпзтапсе) // 1 ( // 2 ацагб иудиагд()оск ); // 3 41 (!р1пзтапсе ) // 4 ( р1пзтапсе = пеи 5зпд)етоп; гетцгп "р1пзтапсе 169 Глава 6.
Реализация шаблона 8~пд!е1оп Допустим, что по~ок управления входит в зону неопределенности (строка 2). В эту точку могут войти сразу несколько потоков. Однако в синхронизированный раздел входит только один поток. В строке 3 неопределенности вообше нет. Здесь все ясно: указатель либо полностью проинициализирован, либо не проинициализирован вообше, Первый поток, который входит в этот раздел, инициализирует переменную, а все остачьные не пройдут проверки на строке 4 и ничего не создадут. Первая проверка быстрая и грубая. Если объект класса 5(по1етоп доступен, вы его получаете. Если нет, необходимы дальнейшие исследования.
Вторая проверка медленна и точна: она сообшает, действительно ли проинициализирован синглтон, или это должен сделать поток. В этом и заключается суть блокировки с двойной проверкой. Теперь мы получаем большое преимушество: скорость доступа к синглтону высока настолько, насколько позволяет сам объект. Однако во время создания объекта класса 5(по)есоп состязание больше не возникает. И все же... Программисты. имеюшие очень большой опыт работы с потоками, знают, что даже шаблон блокировки с двойной проверкой, правильный на бумаге, не всегда хорошо работает на практике.
В некоторых симметричных мультипроцессорных системах (с так называемой релаксированной моделью памяти) порции информации загружаются в память одновременно, а не последовательно. Эти порции записываются по возрастанию адресов, а не в хронологическом порядке. Из-за такого способа упорядочения записей память, просматриваемая одним процессором, может выглядеть так, будто порядок операций, выполненных другим процессором, был неправильным. Например, присваивание значения переменной ртпзгапсе може~ выполняться до завершения полной инициализации объекта класса 5(пй1етоп! Таким образом, увы, шаблон блокировки с двойной проверкой оказался непригодным для ~аких систем. В заключение следует заметить, что перед реализацией шаблона поцЫе-Озес!сег) ьосйтпц нужно просмотреть документацию компилятора.
(Тогда его можно назвать шаблоном тг(р1е-с)зесКед ьосйз по (блокировка с тремя проверками).) Обычно операционная система предоставляет альтернативные, машинно-зависимые средства решения проблемы параллелизма, например, барьеры, упорядочиваюшие доступ к памяти (гпегпо|у Ьагпец). По крайней мере, поставьте перед переменной ртпзтапсе спецификатор чо1ат)1е. Хороший компилятор должен генерировать правильный код вокруг таких объектов. 6.10. Сборка В этой главе обсуждаются разные реализации класса 5зпо)етоп, выявляются их относительные преимушества и недостачи. Не следует думать, что в результате мы сможем прийти к универсальному решению, поскольку каждая задача предъявляет к реализации класса 5зпо1етоп свои требования.
Шаблонный класс 5зпй1етопно1дег, определенный в библиотеке ).оЫ, представляет собой контейнер для синглтонов, позволяюший применить шаблон проектирования 5(пй1егоп. Следуя шаблону проектирования, основанному на применении стратегий (глава!), класс 5зпй1егопно1дег разработан как специализированный контейнер для объектов класса 5зпо1егоп, определенного пользователем. При использовании класса 5зпо1егопно1дег программист получает все необходимые функциональные возможности и может создавать свой собственный код.
В крайнем случае придется все переделать заново (поэтому этот случай и называют крайним). В этой главе рассмотрено несколько тем, практически не связанных друг с другом. Как же теперь реализовать класс 5з пй1етоп, не раздувая размер программы? Для э~ого нужно Часть (!.
Компоненты тщательно разложить шаблон 51пп1етоп на стратегии, как показано в главе !. Разложив класс 5!пя1етопно10ег на несколько стратегий, можно реализовать все варианты, рассмотренные выше, с помощью кода, состоящего из небольшого количества строк. Используя конкретизацию шаблонов, можно отобрать желательные свойства и пренебречь ненужными. Это очень важно: реализация класса 5! пп1етоп не универсальна. Используются лишь те свойства, которые в итоге будут включены в сгенерированный код.
Кроме того, реализация оставляет возможности для изменения и расширений. 6. Ю. 1. Разложение класса 6зпд!есопНо!Нег на стратегии Начнем с выделения стратегий из описанной выше реализации. В ней можно илентифицировать вопросы, связанные с созданием объекта, его продолжительностью жизни и потоками. Это три наиболее важных момента в разработке синглтонов. Рассмотрим соответствующие стратегии. !. Стратегия сгеат1оп. Синглтон можно создать разными способами. Обычно лля создания объекта стратегия сгеат!оп использует оператор пеп.
Выделение процесса созлания объекта в виде отдельной стратегии существенно, поскольку зто позволяет создавать полиморфные объекты. 2. Стратегия ь)бет1яе. Различаются следующие стратегии продолжительности жизни объектов. 2.!. Правила языка С++ — последним создан, первым уничтожен. 2.2. Феникс. 2.3.
Синглтон с заданной прололжительностью жизни. 2.4. Бессмертный синглтон (объект, который никогда не уничтожается). 3. Стратегия тйгвасН помоде1. Синглтон может работать в режиме одного потока, в стандартном многопоточном режиме (с мьютексами и блокировкой с двойной проверкой) или использовать платформно-зависимую потоковую модель. Все реализации класса 51пй1етоп должны гарантировать уникальность объекта. Это условие не влияет на выбор стратегии, поскольку его невыполнение нарушает определение синглтона. 6. 10.2.
Требования, предъявляемые к стратегиям классв 6!пдlейзпйо!оег Определим необходимые ограничения, накладываемые классом 5!пп1етопно1дег на свои стратегии. Стратегия сгеатвоп должна создавать и уничтожать объекты, слеловательно, она даскна солержать две соответствующие функции. Таким образом, если класс сгеатог<т> согласован со стратегией Сгват!оп, он должен содержать вызовы следующих функций, т* роЬ) = сгеатог<т>:ндгеатеО; сгеатог<т>::пезтгоу(роЬ3); Обратите внимание на то, что функции сгеате и оезтгоу должны быть статическими членами класса Сгеатог.
Класс 51 пп1есоп не содержит объект, имеющий тип сгеатог,— зто не позволило бы решить проблемы, связанные с его продолжительностью жизни. Стратегия ь11етйве, по существу, должна планировать разрушение обьекта класса 5!пй1етоп, созданного стратегией сгеат1оп. Функциональные возможности стратегии МРет1ве выражаются в ее способности разрушать объект класса 51пп1етоп в заданный момент времени. Кроме того, стратегия ьббет)ее решает, какие действия 171 Глава б. Реализация шаблона 8!пп!е!оп следует предпринять, если приложение нарушает правила языка С++, определяюшие продолжительность жизни синглтона. ° Если синглтон нужно уничтожить, следуя правилам языка С++, стратегия ь11есдае применяет механизм, аналогичный функции всех)с.
° Для феникса стратегия С!1ес!ае .продолжает использовать механизм, аналогичный функции всех!с, но допускает восстановление синглтона. ° Для синглтона с заданной продолжительностью жизни стратегия сз1есдае вызывает функцию зесьопдеибсу, описанную в разделах 6.7 и 6.8, ° При неограниченной продолжительности жизни синглтона стратегия ь11ес!ае не предпринимает никаких действий. В заключение отметим, что стратегия ь11ес!ае содержит две функции: зсйеди1еоезсгисссоп, задаюшую время уничтожения объекта, и опоеадяетегепсе, регламентируюшую поведение программы при обнаружении висячей ссылки.
Предположим, что стратегия ь11есдае реализуется классом ь11ес!ае<т>. Тогда имеют смысл следуюшие выражения. ио)д (*роезсгисс!опяипсс!оп) О; ь11ессае<т>:: вспеди1еоезсгисс!оп(роезсгисстопяипсс)оп); ь11ес!ае<т>: ."опоеадяе1егепсеО; Функция-член вспеди1еоезсгисс1оп получает указатель на функцию, уничтожаюшую объект. Таким образом, стратегию ьз1ессае можно использовать вместе со стратегией сгеасдоп, Не забывайте, что стратегия ь11естае не связана с методами уничтожения объектов, которые относятся к стратегии сгеасзоп.
Единственное предназначение стратегии ьд 1есдае — определять, когда объект должен быть уничтожен. Функция опоеадяеУегепсе генерирует исключительные ситуации всегда, за исключением ситуаций, в которых задействован феникс. В последнем случае стратегия не предусматривает никаких действий. Стратегия тпгеад!пдмоде! описана в приложении. Класс 51пд1есопно1дег поддерживает блокировку только на уровне классов, но не на уровне объектов. По атой причине в любой момент времени сушествует лишь один синглтон. б. 10.3. Сборка класса В!пдlвсопйоЫвг Начнем определение шаблонного класса в)пд1есопно1дег.
Как указывалось в главе 1, для каждой стратегии предназначен отдельный шаблонный параметр. Кроме того, мы пре- дусмотрим шаблонный параметр т, определяюший разновидность синглтона, Шаблонный класс в! пд1есопно1дег сам по себе не определяет синглтон. Он лишь задает его поведе- ние и способы управления им с помошью уже сушествуюшего класса в! пд! есоп. сеар1асе < с1ааа т, сеяр1асе <с1азз> с1аьв сгеас!опяо11су = сгеасеиз!пднеп, сеяр)асе <с1азз> с1аьз ь11ес!веро!!су = оеатаи1сьдтес!ее, сеар1асе <с1азз> с1ааа тйгеасНпдмоде1 = 51пд1етпгеадед > с1азз взпд1есопно1дег риЫ!с: зсас1с тд спзсапсеО ' Часть й. Компоненты рг)часе: // вспомогательные функции зсас1с чо1д оезсго1пцв(пц1есопо; // Защита Бзпц1есопно1оегО; // Аанные суребег тбгеасмпцмоде1<т>: гчо1ас)1етуре 1пзсапсетуре; зсас(с 1пзсапсетуре* р1пзсапсе ; асас1с Ьоо1 безсгоуед ; Вопреки ожиданиям, переменная экземпляра не относится к типу т .
На самом деле она имеет тип тйгеабзпцмобе1<т>::чо1ас11етуре*. Тип тйгеаозпдмоде1<т>::чо1аст 1етуре расширяет тип т или тип чо1ас11е т в зависимости от фактически применяемой модели потоков. Спецификатор чо1ас11е применяется к типам для того, чтобы сообшить компилятору, что значение данного типа может пзменяться несколькими потоками. Зная это, компилятор не станет выполнять некоторые вилы оптимизации (например, запись значений во внутренние регистры), поскольку это может привести к ошибочной работе потоков.