The C++11 standard says about local static variable initialization that it is supposed to be thread safe (http://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables). My question concerns what exactly happens when a lambda is initialized as a static local variable?
Let's consider the following code:
#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;
}
compiled with GCC 5.4, using ThreadSanitizer:
gcc -std=c++11 -fsanitize=thread -fopenmp -g main.cpp -o main -lstdc++
Works fine, ThreadSanitizer gives no errors. Now, if I change the line where the lambda "computeSum" is initialized to this:
static std::function<int(int)> computeSum = [](int number)
{
return 5 + number;
};
The code still compiles, but ThreadSanitizer gives me a warning, saying there is a data race:
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
In any case, the code where ThreadSanitizer reports a data race needs to be executed 5-10 times until the warning mesage appears.
So my question is: is there a conceptional difference between
static auto computeSum = [](int number){ reentrant code returing int };
and
static std::function<int(int)> computeSum = [](int number) {same code returning int};
What makes the first code to work and the second to be a data race?
Edit #1: It seems that there was (is) quite a discussion going on about my question. I found Sebastian Redl's contribution the most helpful, thus I accepted that answer. I just want to summarize, so that people can refer to this. (Please le me know if this is not appropriate on Stack Overflow, I do not really ask stuff here...)
Why is a data race reported?
It was suggested in a comment (by MikeMB) that the problem is related to a bug in the gcc implementation of TSAN (see this and this link). It seems to be correct:
If I compile the code that contains:
static std::function<int(int)> computeSum = [](int number){ ... return int;};
with GCC 5.4, the machine code looks like:
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 <__tsan_read1@plt>
....
whereas, with GCC 6.3, it reads:
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 <__tsan_atomic8_load@plt>
I am not a big master of machine code, but it looks like that in the GCC 5.4 version, __tsan_read1@plt
is used to check whether the static variable is initialized. In comparison, GCC 6.3 generates __tsan_atomic8_load@plt
. I guess the second one is correct, the first one leads to a false positive.
If I compile the version without ThreadSanitizer, GCC 5.4 generates:
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 <__cxa_guard_acquire@plt>
And 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 <__cxa_guard_acquire@plt>
Why is no data race, if I use auto
instead of std::function
?
You might have to correct me here, but probably the compiler "inlines" the auto object, so there is no need to do bookkeeping on whether the static object has been initialized or not.
static auto computeSum = [](int number){ ... return int;};
produces:
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