Потоково-безопасные статические переменные без мьютексирования?
Я помню, что читал, что статические переменные, объявленные внутри методов, не являются потокобезопасными. (См. Что насчет синглтона Майера? Как упомянуто Тоддом Гарднером)
Dog* MyClass::BadMethod()
{
static Dog dog("Lassie");
return &dog;
}
Моя библиотека генерирует код C++ для конечных пользователей для компиляции как часть их приложения. Код, который он генерирует, должен инициализировать статические переменные в поточно-ориентированном кроссплатформенном виде. Я хотел бы использовать boost::call_once
мьютекс инициализации переменной, но затем конечные пользователи подвергаются зависимости Boost.
Есть ли способ для меня сделать это без навязывания дополнительных зависимостей от конечных пользователей?
5 ответов
Вы правы, что подобная статическая инициализация не является поточно-ориентированной ( вот статья, в которой обсуждается, во что ее превратит компилятор)
На данный момент не существует стандартного, поточно-ориентированного, переносимого способа инициализации статических синглетонов. Можно использовать двойную проверку блокировки, но вам нужны потенциально непереносимые библиотеки потоков (см. Обсуждение здесь).
Вот несколько вариантов, если безопасность потоков является обязательным:
- Не ленитесь (загружен): инициализируйте во время статической инициализации. Это может быть проблемой, если другая статическая функция вызывает эту функцию в своем конструкторе, поскольку порядок статической инициализации не определен (см. Здесь).
- Используйте повышение (как вы сказали) или Локи
- Сверните свой собственный синглтон на поддерживаемых платформах (вероятно, следует избегать, если вы не являетесь экспертом по многопоточности)
- Блокируйте мьютекс каждый раз, когда вам нужен доступ. Это может быть очень медленно.
Пример для 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, хотя я не могу вспомнить, какие компоненты в настоящее время.