Пространства имён
Варианты
Действия

Модель памяти

Материал из cppreference.com
< cpp‎ | language

Определяет семантику компьютерной памяти для целей абстрактной машины C++.

Память, доступная программе на C++ — это одна или несколько непрерывных последовательностей байт. Каждый байт в памяти имеет уникальный адрес.

Содержание

[править] Байт

Байт — это наименьшая адресуемая единица памяти. Он определяется как непрерывная последовательность битов, достаточная, чтобы вместить численное значение любого короткого символа UTF-8 (256 различных значений) и (начиная с C++14) любого символа из базового множества исполняемых символов (96 символов, которые должны быть однобайтовыми). Как и C, C++ поддерживает байты размером 8 битов и более.

Типы char, unsigned char и signed char используют один байт для как для хранения, так и для представления значения. Количество доступных битов в байте определено макросом CHAR_BIT или константой std::numeric_limits<unsigned char>::digits.

[править] Память

Память это:

  • объект скалярного типа (арифметический тип, указатель, перечисление или std::nullptr_t)
  • наибольшая непрерывная последовательность битовых полей ненулевой длины

Примечание: разные сущности языка, такие как ссылки и виртуальные функции, могут требовать дополнительной памяти, которая недоступна программам, но управляется реализацией.

struct S {
    char a;     // память #1
    int b : 5;  // память #2
    int c : 11, // память #2 (продолжается)
          : 0,
        d : 8;  // память #3
    struct {
        int ee : 8; // память #4
    } e;
} obj; // Объект 'obj' состоит из четырёх отдельных объектов в терминах памяти

[править] Потоки и гонка данных

Поток выполнения - это единица выполнения программы, которая начинается с вызова функции верхнего уровня средствами std::thread::thread, std::async, или другими.

Любой поток может иметь доступ к любому объекту в программе (объекты с атомарным или потоковым временем жизни могут быть доступны из другого потока через указатель или ссылку).

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

Когда в процессе некоторого вычисления результат вычисления записывается в память, и другое вычисление читает или модифицирует ту же самую память, такие выражения конфликтуют. Программа, в которой присутсвуют два конфликтующих вычисления, содержит гонку данных кроме случаев, когда:

  • оба вычисления выполняются в одном и том же потоке или в одном и том же обработчике сигналов
  • оба конфликтующих вычисления являются атомарными операциями (see std::atomic)
  • одно из вычислений происходит-до другого (см. std::memory_order)

Если гонка данных имеет место, то поведение программы не определено. В частности, освобождение мьютекса std::mutex синхронизировано-с, и следовательно, случается-до захвата того же мьютекса другим потоком, что даёт возможность использовать мьютекс для избежания гонки данных.

int cnt;
auto f = [&]{cnt++;};
std::thread t1{f}, t2{f}, t3{f}; // неопределённое поведение
std::atomic<int> cnt{0};
auto f = [&]{cnt++;};
std::thread t1{f}, t2{f}, t3{f}; // OK

[править] Упорядочивание доступа к памяти

Когда a поток читает a значение памяти, он может увидеть начальное значение, значение, записанное тем же потоком, или значение, записанное другим потоком. О порядке, в котором записи, совершаемые из одних потоков, становятся видимыми другим потокам, смотри std::memory_order.

[править] Продвижение вперёд

[править] Свобода от препятствий

Когда только один поток, незаблокированный в функции стандартной библиотеки, выполняет неблокирующую атомарную функцию, такое выполнение гарантированно завершается (все неблокирующие операции стандартной библиотеки являются свободными от препятствий)

[править] Свобода от блокировок

Когда один или более неблокирующих атомарных функций выполняются конкурентно, по крайней мере одно из них гарантированно завершается. Все неблокирующие операции стандартной библиотеки являются неблокирующими - реализация должна гарантировать, что они не зайдут в бесконечный активный тупик (live lock) с помощью других потоков, например всё время захватывая строку кэша.

[править] Гарантия продвижения

В корректной C++-программе, каждый поток находится в одном из следующих состояний:

  • завершается
  • вызывает функцию ввода-вывода
  • читает или модифицирует volatile объект
  • выполняет атомарную операцию или операцию синхронизации

Ни один поток выполнения не может выполняться бесконечно, не совершая одно из этих наблюдаемых поведений.

Это означает, что программа, содержащая бесконечную рекурсию или бесконечный цикл (средствами for-statement, goto или как-либо иначе) содержит неопределённое поведение, что позволяет компиляторам удалять любой цикл, не содержащий наблюдаемого поведения, не проверяя, завершается ли он на самом деле.

Говорят, что поток совершает продвижение, если он выполняет один из вышеперечисленных шагов выполнения, блокируется в функции стандартной библиотеки, или вызывает атомарную неблокирующую функцию, которая не завершается из-за незаблокированного конкурентного потока.

Конкурентное продвижение вперёд

Если поток предоставляет гарантию конкурентного продвижения вперёд, он совершает продвижение (как определено выше) за конечный период времени, до своего завершения, вне зависимости от того, совершили ли продвижение другие потоки (при их наличии).

Стандарт рекомендует, но не требует, чтобы главный поток и потоки, запущенные средствами std::thread, предоставляли гарантию конкурентного продвижения вперёд.

Параллельное продвижение вперёд

Если поток предоставляет гарантию параллельного продвижения вперёд, от реализации не требуется гарантии, что поток на самом деле совершит продвижение, когда он ещё не выполнил ни один из вышеперечисленных шагов выполнения. Однако, как только поток выполнит какой-либо шаг, он предоставит гарантии конкурентного продвижения вперёд. Это правило описывает поток в пуле потоков, исполняющем задачи в произвольном порядке.

Слабо параллельное продвижение вперёд

Если поток предоставляет гарантии слабо параллельного продвижения вперёд, он не гарантирует продвижения, вне зависимости от того, совершили ли другие потоки продвижение или нет.

Однако таким потокам может гарантироваться продвижение при блокировании с делегированием гарантии продвижения вперёд: если поток P блокируется до завершения потоков множества S, тогда, по крайней мере, один поток из S предоставит гарантию продвижения, равную или строже, чем у P. Когда поток P завершится, поток из S получит больше времени. Когда множество S станет пустым, P разблокируется.

Параллельные алгоритмы стандартной библиотеки C++ блокируются с делегированием продвижения вперёд по завершении неуказанного множества потоков, управляемых библиотекой.

(начиная с C++17)

[править] See also

Справка по CМодель памяти