В стандарте С++ 11 говорится о локальной инициализации статической переменной, что он должен быть потокобезопасным (http://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables). Мой вопрос касается того, что именно происходит, когда лямбда инициализируется как статическая локальная переменная?
Рассмотрим следующий код:
#include <iostream>
#include <functional>
int doSomeWork(int input)
{
static auto computeSum = [](int number)
{
return 5 + number;
};
return computeSum(input);
}
int main(int argc, char *argv[])
{
int result = 0;
#pragma omp parallel
{
int localResult = 0;
#pragma omp for
for(size_t i=0;i<5000;i++)
{
localResult += doSomeWork(i);
}
#pragma omp critical
{
result += localResult;
}
}
std::cout << "Result is: " << result << std::endl;
return 0;
}
скомпилированный с помощью GCC 5.4, используя ThreadSanitizer:
gcc -std=c++11 -fsanitize=thread -fopenmp -g main.cpp -o main -lstdc++
Прекрасно работает, ThreadSanitizer не дает ошибок. Теперь, если я изменю строку, где lambda "computeSum" инициализируется следующим образом:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
Код все еще компилируется, но ThreadSanitizer дает мне предупреждение, говоря, что есть гонка данных:
WARNING: ThreadSanitizer: data race (pid=20887)
Read of size 8 at 0x000000602830 by thread T3:
#0 std::_Function_base::_M_empty() const /usr/local/gcc-5.4_nofutex/include/c++/5.4.0/functional:1834 (main+0x0000004019ec)
#1 std::function<int (int)>::operator()(int) const /usr/local/gcc-5.4_nofutex/include/c++/5.4.0/functional:2265 (main+0x000000401aa3)
#2 doSomeWork(int) /home/laszlo/test/main.cpp:13 (main+0x000000401242)
#3 main._omp_fn.0 /home/laszlo/test/main.cpp:25 (main+0x000000401886)
#4 gomp_thread_start ../../../gcc-5.4.0/libgomp/team.c:118 (libgomp.so.1+0x00000000e615)
Previous write of size 8 at 0x000000602830 by thread T1:
#0 std::_Function_base::_Function_base() /usr/local/gcc-5.4_nofutex/include/c++/5.4.0/functional:1825 (main+0x000000401947)
#1 function<doSomeWork(int)::<lambda(int)>, void, void> /usr/local/gcc-5.4_nofutex/include/c++/5.4.0/functional:2248 (main+0x000000401374)
#2 doSomeWork(int) /home/laszlo/test/main.cpp:12 (main+0x000000401211)
#3 main._omp_fn.0 /home/laszlo/test/main.cpp:25 (main+0x000000401886)
#4 gomp_thread_start ../../../gcc-5.4.0/libgomp/team.c:118 (libgomp.so.1+0x00000000e615)
Location is global 'doSomeWork(int)::computeSum' of size 32 at 0x000000602820 (main+0x000000602830)
Thread T3 (tid=20891, running) created by main thread at:
#0 pthread_create ../../../../gcc-5.4.0/libsanitizer/tsan/tsan_interceptors.cc:895 (libtsan.so.0+0x000000026704)
#1 gomp_team_start ../../../gcc-5.4.0/libgomp/team.c:796 (libgomp.so.1+0x00000000eb5e)
#2 __libc_start_main <null> (libc.so.6+0x00000002082f)
Thread T1 (tid=20889, running) created by main thread at:
#0 pthread_create ../../../../gcc-5.4.0/libsanitizer/tsan/tsan_interceptors.cc:895 (libtsan.so.0+0x000000026704)
#1 gomp_team_start ../../../gcc-5.4.0/libgomp/team.c:796 (libgomp.so.1+0x00000000eb5e)
#2 __libc_start_main <null> (libc.so.6+0x00000002082f)
SUMMARY: ThreadSanitizer: data race /usr/local/gcc-5.4_nofutex/include/c++/5.4.0/functional:1834 std::_Function_base::_M_empty() const
В любом случае код, в котором ThreadSanitizer сообщает о расходе данных, должен выполняться 5-10 раз, пока не появится предупреждение.
Итак, мой вопрос: существует ли принципиальная разница между
static auto computeSum = [](int number){ reentrant code returing int };
и
static std::function<int(int)> computeSum = [](int number) {same code returning int};
Что заставляет первый код работать, а второй - гонке данных?
Изменить # 1: Кажется, что было (есть) довольно дискуссия о моем вопросе. Я нашел вклад Себастьяна Редла наиболее полезным, поэтому я принял этот ответ. Я просто хочу обобщить, чтобы люди могли сослаться на это. (Пожалуйста, дайте мне знать, если это не подходит для, я действительно не спрашиваю здесь...)
Почему сообщается о гонке данных?
В комментарии (MikeMB) было высказано предположение, что проблема связана с ошибкой реализации gcc в TSAN (см. this и эта ссылка). Кажется правильным:
Если я скомпилирую код, содержащий:
static std::function<int(int)> computeSum = [](int number){ ... return int;};
с GCC 5.4, машинный код выглядит следующим образом:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
4011d5: bb 08 28 60 00 mov $0x602808,%ebx
4011da: 48 89 df mov %rbx,%rdi
4011dd: e8 de fd ff ff callq 400fc0 <[email protected]>
....
тогда как с GCC 6.3 он читает:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
4011e3: be 02 00 00 00 mov $0x2,%esi
4011e8: bf 60 28 60 00 mov $0x602860,%edi
4011ed: e8 9e fd ff ff callq 400f90 <[email protected]>
Я не большой мастер машинного кода, но он выглядит так, как в версии GCC 5.4, [email protected]
используется для проверки инициализации статической переменной. Для сравнения, GCC 6.3 генерирует [email protected]
. Я предполагаю, что второй правильный, первый приводит к ложному положительному.
Если я компилирую версию без ThreadSanitizer, GCC 5.4 генерирует:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
400e17: b8 88 24 60 00 mov $0x602488,%eax
400e1c: 0f b6 00 movzbl (%rax),%eax
400e1f: 84 c0 test %al,%al
400e21: 75 4a jne 400e6d <doSomeWork(int)+0x64>
400e23: bf 88 24 60 00 mov $0x602488,%edi
400e28: e8 83 fe ff ff callq 400cb0 <[email protected]>
И GCC 6.3:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
400e17: 0f b6 05 a2 16 20 00 movzbl 0x2016a2(%rip),%eax # 6024c0 <guard variable for doSomeWork(int)::computeSum>
400e1e: 84 c0 test %al,%al
400e20: 0f 94 c0 sete %al
400e23: 84 c0 test %al,%al
400e25: 74 4a je 400e71 <doSomeWork(int)+0x68>
400e27: bf c0 24 60 00 mov $0x6024c0,%edi
400e2c: e8 7f fe ff ff callq 400cb0 <[email protected]>
Почему нет гонки данных, если я использую auto
вместо std::function
?
Возможно, вам придется поправить меня здесь, но, возможно, компилятор "строит" автоматический объект, поэтому нет необходимости делать бухгалтерию о том, был ли статический объект инициализирован или нет.
static auto computeSum = [](int number){ ... return int;};
дает:
static auto computeSum = [](int number)
400e76: 55 push %rbp
400e77: 48 89 e5 mov %rsp,%rbp
400e7a: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400e7e: 89 75 f4 mov %esi,-0xc(%rbp)
//static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
400e81: 8b 45 f4 mov -0xc(%rbp),%eax
400e84: 83 c0 05 add $0x5,%eax
400e87: 5d pop %rbp
400e88: c3 retq