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

StackOverflow https://stackoverflow.com/questions/1052168

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

  1. Не ленитесь (загружено):Инициализируйте во время статической инициализации.Может возникнуть проблема, если другая статическая функция вызовет эту функцию в своем конструкторе, поскольку порядок статической инициализации не определен (см. здесь).
  2. Используйте boost (как вы сказали) или Loki
  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 библиотеки, хотя я не могу вспомнить, какие компоненты в данный момент.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top