К сожалению, ответ Мэтта включает в себя то, что называется двойной проверкой блокировки , которая не поддерживается моделью памяти C / C ++. (Это поддерживается Java 1.5 и более поздними версиями - и я думаю, что .NET - модель памяти.) Это означает, что между временем, когда выполняется проверка pObj == NULL
, и когда блокировка (мьютекс) получена, pObj
может иметь уже назначен в другой теме. Переключение потоков происходит всякий раз, когда этого хочет ОС, а не между «строками» программы (которые не имеют значения после компиляции в большинстве языков).
Кроме того, как признает Мэтт, он использует int
в качестве блокировки, а не примитива ОС. Не делай этого. Правильные блокировки требуют использования инструкций барьера памяти, потенциально очистки строк кэша и т. Д .; используйте примитивы вашей операционной системы для блокировки. Это особенно важно, потому что используемые примитивы могут меняться между отдельными линиями ЦП, на которых работает ваша операционная система; что работает на CPU Foo, может не работать на CPU Foo2. Большинство операционных систем либо изначально поддерживают потоки POSIX (pthreads), либо предлагают их в качестве оболочки для пакета потоков ОС, поэтому часто лучше всего проиллюстрировать примеры с их использованием.
Если ваша операционная система предлагает соответствующие примитивы, и если вам это абсолютно необходимо для производительности, вместо выполнения этого типа блокировки / инициализации вы можете использовать операцию atomic сравнения и swap для инициализации общей глобальной переменной , По сути, то, что вы пишете, будет выглядеть так:
MySingleton *MySingleton::GetSingleton() {
if (pObj == NULL) {
// create a temporary instance of the singleton
MySingleton *temp = new MySingleton();
if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
// if the swap didn't take place, delete the temporary instance
delete temp;
}
}
return pObj;
}
Это работает только в том случае, если безопасно создать несколько экземпляров вашего синглтона (по одному на поток, который вызывает одновременный вызов GetSingleton ()), а затем выбрасывать дополнительные элементы. Функция OSAtomicCompareAndSwapPtrBarrier
, предоставляемая в Mac OS X - большинство операционных систем предоставляют аналогичный примитив - проверяет, является ли pObj
значением NULL
, и фактически устанавливает его на temp
, если это так. При этом используется аппаратная поддержка, чтобы буквально выполнить своп только один раз и определить, произошло ли это.
Еще одно средство, которое можно использовать, если ваша ОС предлагает его, между этими двумя крайностями - pthread_once
. Это позволяет вам настроить функцию, которая запускается только один раз - в основном, выполняя все блокировки / барьеры / и т.д. обман для вас - независимо от того, сколько раз он вызывался или сколько потоков он вызывал.