Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 65
Иногда подходящего уровня гранулярности просто не существует, потому что не все операции доступа к структуре данных требуют одного и того же уровня защиты. В таком случае вместо простого класса >std::mutex
стоит поискать альтернативный механизм.
3.3. Другие средства защиты разделяемых данных
Хотя мьютексы и представляют собой наиболее общий механизм, но они не единственные игроки на поле защиты разделяемых данных. Есть и другие механизмы, обеспечивающие защиту в специальных случаях.
Один такой крайний (но на удивление распространенный) случай возникает, когда разделяемые данные нуждаются в защите от одновременного доступа только на этапе инициализации, а потом уже никакой синхронизации не требуется. Так может быть, например, потому что после инициализации данные только читаются или потому что необходимая защита обеспечивается неявно как часть операций над данными. Как бы то ни было, захватывать мьютекс после того, как данные инициализированы, совершенно не нужно, это только снизило бы производительность. Поэтому в стандарте С++ предусмотрен механизм, служащий исключительно для защиты разделяемых данных во время инициализации.
3.3.1. Защита разделяемых данных во время инициализации
Предположим, имеется разделяемый ресурс, конструирование которого обходится настолько дорого, что мы хотим делать это, лишь когда действительно возникает необходимость; быть может, конструктор открывает базу данных или выделяет очень много памяти. Такая отложенная инициализация часто встречает в однопоточных программах — всякая операция, нуждающаяся в ресурсе, сначала проверяет, инициализирован ли он, и, если нет, выполняет инициализацию:
>std::shared_ptr
>void foo() {
> if (!resource_ptr) {
> resource_ptr.reset(new some_resource); ←
(1)
> }
> resource_ptr->do_something();
>}
Если сам разделяемый ресурс безопасен относительно одновременного доступа, то при переходе к многопоточной реализации единственная нуждающаяся в защите часть — инициализация (1), однако наивный подход, показанный в листинге ниже, может привести к ненужной сериализации использующих ресурс потоков. Дело в том, что каждый поток должен ждать освобождения мьютекса, чтобы проверить, был ли ресурс уже инициализирован.
Листинг 3.11. Потокобезопасная отложенная инициализация с помощью мьютекса
>std::shared_ptr
>std::mutex resource_mutex; ←┐
В этой точке все потоки
> │