Потоково-безопасные статические переменные без мьютексирования?

Я помню, что читал, что статические переменные, объявленные внутри методов, не являются потокобезопасными. (См. Что насчет синглтона Майера? Как упомянуто Тоддом Гарднером)

Dog* MyClass::BadMethod()
{
  static Dog dog("Lassie");
  return &dog;
}

Моя библиотека генерирует код C++ для конечных пользователей для компиляции как часть их приложения. Код, который он генерирует, должен инициализировать статические переменные в поточно-ориентированном кроссплатформенном виде. Я хотел бы использовать boost::call_once мьютекс инициализации переменной, но затем конечные пользователи подвергаются зависимости Boost.

Есть ли способ для меня сделать это без навязывания дополнительных зависимостей от конечных пользователей?

5 ответов

Решение

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

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

Вот несколько вариантов, если безопасность потоков является обязательным:

  1. Не ленитесь (загружен): инициализируйте во время статической инициализации. Это может быть проблемой, если другая статическая функция вызывает эту функцию в своем конструкторе, поскольку порядок статической инициализации не определен (см. Здесь).
  2. Используйте повышение (как вы сказали) или Локи
  3. Сверните свой собственный синглтон на поддерживаемых платформах (вероятно, следует избегать, если вы не являетесь экспертом по многопоточности)
  4. Блокируйте мьютекс каждый раз, когда вам нужен доступ. Это может быть очень медленно.

Пример для 1:

// in a cpp:
namespace {
    Dog dog("Lassie");
}

Dog* MyClass::BadMethod()
{
  return &dog;
}

Пример для 4:

Dog* MyClass::BadMethod()
{
  static scoped_ptr<Dog> pdog;
  {
     Lock l(Mutex);
     if(!pdog.get())
       pdog.reset(new Dog("Lassie"));
  }
  return pdog.get();
}

Не уверен, имеете ли вы это в виду или нет, но вы можете удалить зависимость boost в системах POSIX, вызвав pthread_once вместо. Я предполагаю, что вам придется сделать что-то другое в Windows, но избегая этого, именно поэтому у boost в первую очередь есть библиотека потоков, и почему люди платят за это в зависимости от нее.

Делать что-либо "потокобезопасным" по своей сути связано с вашей реализацией потоков. Вы должны зависеть от чего-то, даже если это только модель памяти, зависящая от платформы. В чистом C++03 просто невозможно что-либо предположить о потоках, которые находятся за пределами языка.

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

static Dog dog("Lassie");
Dog* MyClass::BadMethod()
{
  return &dog;
}

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

Единственный известный мне способ гарантировать, что у вас не возникнет проблем с не защищенными ресурсами, такими как ваш "static Dog" это требование, чтобы они были созданы до создания каких-либо потоков.

Это может быть так же просто, как просто документировать, что они должны вызвать MyInit() Функция в главном потоке, прежде чем делать что-либо еще. Затем вы строите MyInit() создать и уничтожить один объект каждого типа, который содержит одну из этих статик.

Единственная другая альтернатива - это наложить другое ограничение на то, как они могут использовать ваш сгенерированный код (использовать Boost, потоки Win32 и т. Д.). По моему мнению, любое из этих решений является приемлемым - можно создавать правила, которым они должны следовать.

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

AFAIK, единственный раз, когда это было сделано безопасно и без мьютексов или предварительной инициализации глобальных экземпляров, находится в Несовершенном C++ Мэтью Уилсона, где обсуждается, как это сделать с помощью "спинового мьютекса". Я не рядом с моей копией, поэтому не могу сказать вам более точно в это время.

IIRC, есть несколько примеров использования этого в библиотеках STLSoft, хотя я не могу вспомнить, какие компоненты в настоящее время.

Другие вопросы по тегам