В этой главе:

■ Детальные сведения о модели памяти С++.

■ Атомарные типы в стандартной библиотеке С++.

■ Операции над атомарными типами.

■ Как можно использовать эти операции для синхронизации потоков.

Одна из самых важных особенностей стандарта С++11 — та, которую большинство программистов даже не замечают. Это не новые синтаксические конструкции и не новые библиотечные средства, а новая модель памяти, учитывающая многопоточность. Без модели памяти, которая точно определяет, как должны работать основополагающие строительные блоки, ни на одно из описанных выше средств нельзя было бы полагаться. Понятно, почему большинство программистов этого не замечают: если вы пользуетесь для защиты данных мьютексами, а для сигнализации о событиях — условными переменными или будущими результатам, то вопрос о том, почему они работают, не так уж важен. И лишь когда вы подбираетесь «ближе к железу», становятся существенны точные детали модели памяти.

С++ используется для решения разных задач, но одна из основных — системное программирование. Поэтому комитет по стандартизации в числе прочих целей ставил и такую: сделать так, чтобы в языке более низкого уровня, чем С++, не возникало необходимости. С++ должен обладать достаточной гибкостью, чтобы программист мог сделать то, что хочет, без помех со стороны языка, в том числе и работать «на уровне железа». Атомарные типы и операции — шаг именно в этом направлении, поскольку они предоставляют низкоуровневые механизмы синхронизации, которые обычно транслируются в одну-две машинные команды.

В этой главе мы начнем с рассмотрения основ модели памяти, затем перейдем к атомарным типам и операциям и в конце обсудим различные виды синхронизации, реализуемые с помощью операций над атомарными типами. Это довольно сложная тема; если вы не планируете писать код, в котором атомарные операции используются для синхронизации (например, структуры данных без блокировок, рассматриваемые в главе 7), то все эти детали вам ни к чему. Но давайте потихоньку двинемся вперёд и начнем с модели памяти.

<p>5.1. Основы модели памяти</p>

У модели памяти есть две стороны: базовые структурные аспекты, относящиеся к размещению программы в памяти, и аспекты, связанные с параллелизмом. Структурные аспекты важны для параллелизма, особенно если опуститься на низкий уровень атомарных операций, поэтому с них я и начну. В С++ всё вращается вокруг объектов и ячеек памяти.

<p>5.1.1. Объекты и ячейки памяти</p>

Любые данные в программе на С++ состоят из объектов. Это не значит, что можно создать новый класс, производный от int, или что у фундаментальных типов есть функции-члены, или вообще нечто такое, что часто имеют в виду, когда говорят «нет ничего, кроме объектов» при обсуждении таких языков, как Smalltalk или Ruby. Это утверждение просто означает, что в С++ данные строятся из объектов. В стандарте С++ объект определяется как «область памяти», хотя далее речь идет о таких свойствах объектов, как тип и время жизни.

Некоторые объекты являются простыми значениями таких фундаментальных типов, как int или float, другие — экземплярами определенных пользователем классов. У некоторых объектов (например, массивов, экземпляров производных классов и экземпляров классов с нестатическими данными-членами) есть подобъекты, у других — нет.

Вне зависимости от типа объект хранится в одной или нескольких ячейках памяти. Каждая такая ячейка — это либо объект (или подобъект) скалярного типа, например unsigned short или my_class*, либо последовательность соседних битовых полей. Если вы пользуетесь битовыми полями, то имейте в виду один важный момент: хотя соседние битовые поля является различными объектами, они тем не менее считаются одной ячейкой памяти. На рис. 5.1 показано, как структура struct представлена в виде совокупности объектов и ячеек памяти.

Рис. 5.1. Разбиение struct на объекты и ячейки памяти

Во-первых, вся структура — это один объект, который состоит из нескольких подобъектом, по одному для каждого члена данных. Битовые поля bf1 и bf2 занимают одну ячейку памяти, объект s типа std::string занимает несколько ячеек памяти, а для каждого из остальных членов отведена своя ячейка. Обратите внимание, что битовое поле нулевой длины bf3 заставляет отвести для bf4 отдельную ячейку.

Отсюда можно сделать несколько важных выводов:

• каждая переменная — объект, в том числе и переменные, являющиеся членами других объектов;

• каждый объект занимает по меньшей мере одну ячейку памяти;

• переменные фундаментальных типов, например int или char, занимают в точности одну ячейку памяти вне зависимости от размера, даже если являются соседними или элементами массива;

Перейти на страницу:

Похожие книги