Ориентированная на многопотоковое исполнение инициализация функционально-локальных статических объектов константы

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

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

namespace {
  const some_type& create_const_thingy()
  {
     lock my_lock(some_mutex);
     static const some_type the_const_thingy;
     return the_const_thingy;
  }
}

void use_const_thingy()
{
  static const some_type& the_const_thingy = create_const_thingy();

  // use the_const_thingy

}

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

Мне было бы интересно, если это

  1. достаточно безопасный на практике?
  2. безопасный согласно Правилам? (Я знаю, текущий стандарт даже не знает, каков "параллелизм", но что относительно того, чтобы растоптать по уже инициализированной ссылке? И другие стандарты, как POSIX, имеют что-то, чтобы сказать, что это относится к этому?)

Причина я хочу знать это, состоит в том, что я хочу знать, могу ли я оставить код, как это или должен ли я возвратиться и зафиксировать это.


Для пытливых умов:

Много таких функционально-локальных статических объектов константы, которые я использовал, являются картами, которые инициализируются от массивов константы после первого использования и используются для поиска. Например, у меня есть несколько синтаксических анализаторов XML, где строки имени тега отображаются на enum значения, таким образом, я мог позже switch по тегам enum значения.


Так как я получил некоторые ответы относительно того, что сделать, вместо этого, но иметь не ответ на мои фактические вопросы (см. 1. и 2. выше), я запущу щедрость на этом. Снова:
Я не интересуюсь тем, что я мог сделать вместо этого, я действительно хочу знать об этом.

19
задан Community 23 May 2017 в 11:53
поделиться

6 ответов

Это моя вторая попытка ответить. Я отвечу только на первый из ваших вопросов:

  1. достаточно ли безопасно на практике?

Нет. Как вы утверждаете, вы только обеспечиваете защиту создания объекта, а не инициализацию ссылки на объект.

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

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

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

14
ответ дан 30 November 2019 в 04:47
поделиться

Я не являюсь стандартистом ...

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

Ленивый способ имеет смысл только в том случае, если вы используете это значение из другого «глобального».

С другой стороны, еще один метод, который я видел, заключался в использовании какой-то координации:

  • 'Singleton' для регистрации своего метода инициализации в объекте 'GlobalInitializer' во время загрузки библиотеки
  • 'GlobalInitializer' вызывается в 'main' перед запуском любого потока

, хотя я, возможно, не описываю это точно.

0
ответ дан 30 November 2019 в 04:47
поделиться

Вот мой вариант (если вы действительно не можете инициализировать его до запуска потоков):

Я видел (и использовал) что-то подобное для защиты статической инициализации, используя boost :: once

#include <boost/thread/once.hpp>

boost::once_flag flag;

// get thingy
const Thingy & get()
{
    static Thingy thingy;

    return thingy;
}

// create function
void create()
{
     get();
}

void use()
{
    // Ensure only one thread get to create first before all other
    boost::call_once( &create, flag );

    // get a constructed thingy
    const Thingy & thingy = get(); 

    // use it
    thingy.etc..()          
}

In Насколько я понимаю, таким образом все потоки ждут boost :: call_once, кроме одного, который создаст статическую переменную. Он будет создан только один раз и больше никогда не будет вызван. И тогда у вас больше нет блокировки.

5
ответ дан 30 November 2019 в 04:47
поделиться

Вкратце, я думаю, что:

  • Инициализация объекта является поточно-ориентированной, предполагая, что "some_mutex" полностью сконструирован при вводе "create_const_thingy".

  • Не гарантируется, что инициализация ссылки на объект внутри use_const_thingy будет потокобезопасной; он может (как вы говорите) подвергаться многократной инициализации (что менее проблематично), , но также может быть подвержен разрыву слов , что может привести к неопределенному поведению.

[Я предполагаю, что ссылка C ++ реализована как ссылка на фактический объект с использованием значения указателя, которое теоретически может быть прочитано при частичной записи].

Итак, чтобы попытаться ответить на ваш вопрос:

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

  2. Безопасно согласно правилу: ну, извините, в C ++ 98 таких правил нет (но вы это уже знали).


Обновление: После публикации этого ответа я понял, что он фокусируется только на небольшой, эзотерической части реальной проблемы, и из-за этого решил опубликовать другой ответ вместо редактирования содержания. Я оставляю содержание «как есть», поскольку оно имеет некоторое отношение к вопросу (а также для того, чтобы смириться, напоминая мне, чтобы я подумал еще немного, прежде чем отвечать).

0
ответ дан 30 November 2019 в 04:47
поделиться

Итак, соответствующая часть спецификации - 6.7/4:

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

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

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

Обновление

Итак, что касается вызова конструктора some_type для the_const_thingy, ваш код корректен в соответствии с правилами.

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

3
ответ дан 30 November 2019 в 04:47
поделиться

Просто вызовите функцию перед тем, как начать создавать потоки, тем самым гарантируя ссылку и объект. В качестве альтернативы, не используйте такой по-настоящему ужасный шаблон проектирования. Я имею в виду, зачем вообще статическая ссылка на статический объект? Зачем вообще статические объекты? В этом нет никакой пользы. Синглтоны - ужасная идея.

-1
ответ дан 30 November 2019 в 04:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: