Подтвердить что ты не робот

Статическая локальная переменная инициализации в многопоточной среде

Предположим, что существует функция (возможно, функция-член)

SomeType foo()
{
    static SomeType var = generateVar();
    return var;
}

Как var будет инициализироваться, если foo будет вызываться "в первый раз" из нескольких потоков одновременно?

  • Гарантировано ли, что generateVar() будет вызываться только один раз в любом сценарии (если используется, конечно)?
  • Гарантируется ли, что foo будет возвращать одно и то же значение при вызове несколько раз в любом сценарии?
  • Есть ли разница в поведении для примитивных или не-примитивных типов?
4b9b3361

Ответ 1

Что касается С++ 03:

Абстрактная машина, определенная стандартом С++ 03, не содержит формального определения того, что такое поток, и каков результат программы, если к объекту обращаются одновременно.

Не существует понятия примитива синхронизации, упорядочения операций, выполняемых в разных потоках, расы данных и т.д. Поэтому, по определению, каждая многопоточная программа С++ 03 содержит поведение undefined.

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

Остальная часть ответа будет сосредоточена на С++ 11, которая определяет семантику параллельных операций.

Что касается С++ 11:

Гарантируется ли, что generateVar() будет вызываться только один раз в любом сценарии (если используется, конечно)?

Нет, не в любом сценарии.

Инициализация var гарантируется как потокобезопасная, поэтому generateVar() не будет вводиться одновременно, но если исключение выбрано generateVar() или конструктором копирования или конструктором перемещения SomeType (если SomeType - это UDT, конечно), то инициализация будет повторно предпринята в следующий раз, когда поток выполнения войдет в объявление, что означает, что generateVar() снова будет вызван.

В параграфе 6.7/4 Стандарта С++ 11 при инициализации переменных области видимости блока со статической продолжительностью хранения:

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

Относительно вашего следующего вопроса:

Гарантировано ли, что foo вернет одно и то же значение при вызове несколько раз в любом сценарии?

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

Есть ли разница в поведении для примитивных или не-примитивных типов?

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