Является статическим init потокобезопасным с VC2010?
Я искал все вокруг SO и MSDN для ответа на этот вопрос, но не могу найти четкого и окончательного ответа...
Я знаю, что это в стандарте С++ 11, и эта текущая версия GCC ведет себя таким образом, но VC2010 в настоящее время гарантирует безопасность потоков локальной инициализации статической переменной?
i.e: Является ли это потокобезопасным с VC2010?
static S& getInstance()
{
static S instance;
return instance;
}
... И если нет, то в чем заключается лучшая практика для получения потокобезопасной реализации singleton в С++ с помощью VC2010?
РЕДАКТИРОВАТЬ: Как отметил Крис Бетти, VC2010 не реализует потокобезопасность локальной статической переменной init.
Ответы
Ответ 1
Из Visual Studio 2010 документация по Static:
Назначение значения статической локальной переменной в многопоточном приложении не является потокобезопасным, и мы не рекомендуем его в качестве практики программирования.
Во второй части вашего вопроса есть хорошие хорошие ответы.
Обновлено 22 ноября 2015 г.
Другие проверяют, в частности, что статическая инициализация также не является потокобезопасной (см. комментарий и другой ответ).
Пользовательский squelart на VS2015:
вы можете добавить, что VS2015, наконец, правильно: https://msdn.microsoft.com/en-au/library/hh567368.aspx#concurrencytable ( "Magic statics" )
Ответ 2
В следующем фрагменте кода показано, что "локализованная статическая инициализация объектов" не является потокобезопасной:
#include <windows.h>
#include <stdio.h>
#include <process.h>
struct X {
~X() { puts("~X()"); }
int i_ ;
void print(void) {
printf("thread id=%u, i = %d\n", GetCurrentThreadId(), i_);
}
X(int i) {
puts("begin to sleep 10 seconds");
Sleep(1000 * 10);
i_ = i;
printf("X(int) i = %d\n", i_);
puts("end");
}
};
X & getX()
{
static X static_x(1000);
return static_x;
}
void thread_proc(void *)
{
X & x = getX();
x.print();
}
int main(int argc, char *argv[])
{
HANDLE all_threads[2] = {};
all_threads[0] = HANDLE( _beginthread(thread_proc, 0, 0) );
printf("First thread Id: %u\n", GetThreadId(all_threads[0]) );
Sleep(1000);
all_threads[1] = HANDLE( _beginthread(thread_proc, 0, 0) );
printf("Second thread Id: %u\n", GetThreadId(all_threads[1]) );
WaitForMultipleObjects( _countof(all_threads), all_threads, TRUE, 1000 * 20);
puts("main exit");
return 0;
}
Выход будет (конечно, идентификатор потока будет отличаться на вашем компьютере):
First thread Id: 20104
begin to sleep 10 seconds
Second thread Id: 20248
thread id=20248, i = 0
X(int) i = 4247392
end
thread id=20104, i = 1000
main exit
~X()
Прежде чем первый поток возвращает, что означает, что singleton ctor вызывается и возвращается, второй поток получает неинициализированный объект и вызывает его метод-член (поскольку статический объект находится в сегменте BSS, он будет инициализирован до нуля после загрузчик загружает исполняемый файл) и получает неправильное значение: 0.
Включение списка сборки /FAsc/Fastatic.asm получит код сборки для функции getX():
01: [email protected]@[email protected]@XZ PROC ; getX
02:
03: ; 20 : {
04:
05: 00000 55 push ebp
06: 00001 8b ec mov ebp, esp
07:
08: ; 21 : static X static_x(1000);
09:
10: 00003 a1 00 00 00 00 mov eax, DWORD PTR [email protected][email protected]@[email protected]@[email protected]
11: 00008 83 e0 01 and eax, 1
12: 0000b 75 2b jne SHORT [email protected]
13: 0000d 8b 0d 00 00 00
14: 00 mov ecx, DWORD PTR [email protected][email protected]@[email protected]@[email protected]
15: 00013 83 c9 01 or ecx, 1
16: 00016 89 0d 00 00 00
17: 00 mov DWORD PTR [email protected][email protected]@[email protected]@[email protected], ecx
18: 0001c 68 e8 03 00 00 push 1000 ; 000003e8H
19: 00021 b9 00 00 00 00 mov ecx, OFFSET [email protected][email protected]@[email protected]@[email protected]@A
20: 00026 e8 00 00 00 00 call [email protected]@[email protected]@Z ; X::X
21: 0002b 68 00 00 00 00 push OFFSET [email protected][email protected]@[email protected]@[email protected] ; `getX'::`2'::`dynamic atexit destructor for 'static_x''
22: 00030 e8 00 00 00 00 call _atexit
23: 00035 83 c4 04 add esp, 4
24: [email protected]:
25:
26: ; 22 : return static_x;
27:
28: 00038 b8 00 00 00 00 mov eax, OFFSET [email protected][email protected]@[email protected]@[email protected]@A
29:
30: ; 23 : }
В строке 10 критический символ [? $S1 @? 1?? getX @@YAAAUX @@XZ @4IA] является глобальным индикатором (также в BSS), который указывает, является ли одиночный элемент установленным или нет, это будет как и в строке 14-17, как раз перед вызовом в ctor, эта проблема также объясняет, почему второй поток сразу же получил неинициализированный одноэлементный объект и счастливо называет его функцией-членом. В компиляторе не вставлен код, связанный с безопасностью потока.