Потоково-безопасные статические переменные без мьютексирования? - PullRequest
13 голосов
/ 27 июня 2009

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

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

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

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

Ответы [ 5 ]

10 голосов
/ 27 июня 2009

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

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

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

  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();
}
4 голосов
/ 27 июня 2009

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

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

3 голосов
/ 27 июня 2009

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

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

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

2 голосов
/ 27 июня 2009

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

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

2 голосов
/ 27 июня 2009

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

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

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

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

...