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