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

Инициализация потоков локальных статических константных объектов

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

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

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

}

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

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

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

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


Для вопрошающих умов:

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


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

4b9b3361

Ответ 1

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

  • достаточно безопасен на практике?

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

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

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

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

Ответ 2

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

Я видел (и использовал) что-то вроде этого, чтобы защитить статическую инициализацию, используя 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..()          
}

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

Ответ 3

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

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

Предполагая, что выполняется вторая часть (object is initialized the first time control passes through its declaration), ваш код можно считать безопасным для потоков.

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

Обновление

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

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

Ответ 4

Я не стандартист...

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

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

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

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

хотя я не могу точно его описывать.

Ответ 5

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

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

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

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

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

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

  • Безопасно в соответствии с правилом: Ну, таких правил нет в С++ 98, извините (но вы уже это знали).


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

Ответ 6

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

Ответ 7

Это, кажется, самый простой/самый чистый подход, о котором я могу думать, не требуя всех шананиганов мьютекса:

static My_object My_object_instance()
{
    static My_object  object;
    return object;
}

// Ensures that the instance is created before main starts and creates any threads
// thereby guaranteeing serialization of static instance creation.
__attribute__((constructor))
void construct_my_object()
{
    My_object_instance();
}