У меня есть следующий класс
class Singleton
{
private:
static Singleton *p_inst;
Singleton();
public:
static Singleton * instance()
{
if (!p_inst)
{
p_inst = new Singleton();
}
return p_inst;
}
};
Уточните меры предосторожности, принятые при реализации Singleton в многопоточной среде.
В многопоточности этот раздел
if(!p_inst)
{
p_inst = new Singleton();
}
фактически представляет собой 3 отдельных действия. Вы получаете значение p_inst
, устанавливаете значение p_inst
и записываете значение p_inst
. Таким образом, get-set-write означает, что вам нужно установить блокировку вокруг p_inst
, иначе у вас может быть 2 потока, которые создают значение Singleton
, которое использует каждый поток.
Вот как вы можете увидеть проблему, предположим, что ваш Синглтон
имеет изменяемое поле val
:
thread A -> p_inst is NULL
thread B -> p_inst is NULL
thread A -> set to Singleton (1)
thread B -> set to Singleton (2)
thread C -> p_inst is Singleton (2)
thread A -> set val to 4
thread B -> set val to 6
thread C -> get val (it's 6)
thread A -> get val (it's 4!!)
Понимаете? Вокруг плавают 2 копии синглтона, ни одна из которых не знает друг о друге. Третий поток, проверяющий Singleton
, увидит только последнее назначение. Но с помощью блокировки вы можете предотвратить множественное присвоение и подобные проблемы.
Для многопоточного построения используйте статическую переменную в функции instance (). Инициализация статических переменных автоматически защищена компилятором. Любые другие операции требуют явной блокировки. Используйте мьютексы.
class Singleton
{
private:
Singleton();
public:
static Singleton * instance()
{
static Singleton inst;
return &inst;
}
};
Вам нужно будет использовать мьютекс и заблокировать указатель перед его назначением или чтением, что делает этот шаблон проектирования медленным (и я просто ужасным).
Буду краток: это зависит от вашего компилятора.
Теперь вы должны понять, что вам это может и не понадобиться.
Есть два способа справиться с этим, которые не требуют осознания многопоточности.
статический
экземпляр вместо динамического выделения. Безопасно и просто. Может возникнуть проблема с порядком инициализации, если вам нужно получить доступ к нему из другой static
переменнойmain
. Конечно, главный вопрос в том, не можете ли вы просто передать ссылку на объект, а не создавать глобальную переменную? Это бы упростило тестирование ;)
Вы должны спросить себя, что вы подразумеваете под безопасностью потоков.
Действительно ли вашему синглтону нужна потокобезопасность?
Если нет, рассмотрите потоково-статический подход
Вы хотите гарантировать, что не будут созданы два экземпляра синглтона?
Если нет, то ваше решение, приведенное выше, вероятно, подойдет без какой-либо блокировки: you ' У меня есть состояние гонки при строительстве - но вас не волнует , поскольку в конечном итоге выживет только один - однако у вас может произойти утечка ресурсов, если вы не будете осторожны, что может быть или не быть значительным. (По сути, это кеш).
Вы хотите гарантировать, что в конечном итоге останется только один экземпляр?
Вы заботитесь о затратах на блокировку?
Если нет (что довольно часто), вы можете просто заблокировать его и быть счастливым.
Синглтон - это шаблон, который может решать различные проблемы, но то, какой вид безопасности потоков требуется, имеет мало общего с самим одноэлементным шаблоном и все, что связано с тем, для чего он нужен.