Spec-Zone.ru › C++

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

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

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

Байты

Байт — это наименьшая адресуемая единица памяти. Он определяется как непрерывная последовательность битов, достаточно большая, чтобы содержать

  • значение любого UTF-8 кодового элемента (256 различных значений) и
  • любой член базового набора символов выполнения basic execution character set.
(до C++23)
  • обычное кодирование любого элемента базового набора символов литералов basic literal character set.
(с C++23)

Подобно C, C++ поддерживает байты размером 8 бит и больше.

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

Местоположение памяти

Местоположение памяти — это

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

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

struct S
{
    char a;     // memory location #1
    int b : 5;  // memory location #2
    int c : 11, // memory location #2 (continued)
          : 0,
        d : 8;  // memory location #3
    struct
    {
        int ee : 8; // memory location #4
    } e;
} obj; // The object 'obj' consists of 4 separate memory locations

Потоки и гонки данных

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

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

Разным потокам исполнения всегда разрешено одновременно обращаться (читать и изменять) разные местоположения памяти без взаимного влияния и без требований синхронизации.

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

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

Если происходит гонка данных, поведение программы не определено.

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

int cnt = 0;
auto f = [&] { cnt++; };
std::thread t1{f}, t2{f}, t3{f}; // undefined behavior
std::atomic<int> cnt{0};
auto f = [&] { cnt++; };
std::thread t1{f}, t2{f}, t3{f}; // OK

Порядок памяти

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

Прогресс

Свобода от блокировок

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

Свобода от блокировок

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

Гарантия прогресса

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

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

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

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

Конкурентный прогресс

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

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

Параллельный прогресс

Если поток предоставляет гарантию параллельного прогресса, реализация не обязана обеспечивать, что поток в конечном итоге будет прогрессировать, если он еще не выполнил ни одной операции (ввода-вывода, volatile, атомной или синхронизации), но как только этот поток выполнит операцию, он предоставляет гарантии конкурентного прогресса (это правило описывает поток в пуле потоков, который выполняет задачи в произвольном порядке).

Слабый параллельный прогресс

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

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

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

(с C++17)

См. также

Документация C для Модели памяти

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/memory_model

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API