К сожалению, в ответе Мэтта есть так называемая блокировка с двойной проверкой, которая не поддерживается моделью памяти C / C ++. (Он поддерживается моделью памяти Java 1.5 и более поздних - и, я думаю, .NET -.) Это означает, что между моментом, когда выполняется проверка pObj == NULL
и когда происходит блокировка (мьютекс), pObj
, возможно, уже был назначен на другой поток. Переключение потоков происходит всякий раз, когда этого требует ОС, а не между «строками» программы (которые не имеют значения после компиляции на большинстве языков).
Более того, как признает Мэтт, он использует int
как блокировку, а не примитив ОС. Не делай этого. Правильные блокировки требуют использования инструкций барьера памяти, потенциально сброса строки кэша и так далее; используйте примитивы вашей операционной системы для блокировки. Это особенно важно, потому что используемые примитивы могут меняться между отдельными линиями ЦП, на которых работает ваша операционная система; то, что работает на CPU Foo, может не работать на CPU Foo2. Большинство операционных систем либо изначально поддерживают потоки POSIX (pthreads), либо предлагают их в качестве оболочки для пакета потоковой передачи ОС, поэтому часто лучше всего иллюстрировать примеры с их помощью.
Если ваша операционная система предлагает соответствующие примитивы и если вам это абсолютно необходимо для повышения производительности, вместо выполнения этого типа блокировки / инициализации вы можете использовать операцию атомарного сравнения и замены для инициализации общей глобальной переменной. По сути, то, что вы напишете, будет выглядеть так:
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
. Это позволяет вам настроить функцию, которая запускается только один раз - в основном, выполняя все блокировки / барьеры / и т. Д. обман для вас - независимо от того, сколько раз он вызывается или в скольких потоках он вызывается.
person
Chris Hanson
schedule
09.08.2008