Currently, helgrind is not aware of any of the atomic types from the C++11 standard library, producing heaps of false positives. Reproducible: Always Steps to Reproduce: mic@mic-nb /tmp $ cat test.cpp #include <atomic> #include <cstdio> #include <thread> int main() { std::atomic_bool a{false}; std::thread t{[&a]() { a = true; std::printf("%i\n", a.load()); }}; a = false; std::printf("%i\n", a.load()); t.join(); } mic@mic-nb /tmp $ g++ -std=c++11 -lpthread test.cpp mic@mic-nb /tmp $ valgrind --tool=helgrind ./a.out Actual Results: False positives are reported for each access to the atomic variable Expected Results: Program reported as clean helgrind output: ==22370== Helgrind, a thread error detector ==22370== Copyright (C) 2007-2013, and GNU GPL'd, by OpenWorks LLP et al. ==22370== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info ==22370== Command: ./a.out ==22370== 1 ==22370== ---Thread-Announcement------------------------------------------ ==22370== ==22370== Thread #1 is the program's root thread ==22370== ==22370== ---Thread-Announcement------------------------------------------ ==22370== ==22370== Thread #2 was created ==22370== at 0x596A3AE: clone (in /usr/lib/libc-2.20.so) ==22370== by 0x4E425A4: do_clone.constprop.4 (in /usr/lib/libpthread-2.20.so) ==22370== by 0x4E43A6C: pthread_create@@GLIBC_2.2.5 (in /usr/lib/libpthread-2.20.so) ==22370== by 0x4C302CD: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) ==22370== by 0x5112E78: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib/libstdc++.so.6.0.20) ==22370== by 0x400EB5: std::thread::thread<main::{lambda()#1}>(main::{lambda()#1}&&) (in /tmp/a.out) ==22370== by 0x400D81: main (in /tmp/a.out) ==22370== ==22370== ---------------------------------------------------------------- ==22370== ==22370== Possible data race during write of size 1 at 0xFFEFFFBBF by thread #1 ==22370== Locks held: none ==22370== at 0x401FC1: std::__atomic_base<bool>::operator=(bool) (in /tmp/a.out) ==22370== by 0x401D88: std::atomic_bool::operator=(bool) (in /tmp/a.out) ==22370== by 0x400D92: main (in /tmp/a.out) ==22370== ==22370== This conflicts with a previous write of size 1 by thread #2 ==22370== Locks held: none ==22370== at 0x401FC1: std::__atomic_base<bool>::operator=(bool) (in /tmp/a.out) ==22370== by 0x401D88: std::atomic_bool::operator=(bool) (in /tmp/a.out) ==22370== by 0x400D2F: main::{lambda()#1}::operator()() const (in /tmp/a.out) ==22370== by 0x401D0B: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (in /tmp/a.out) ==22370== by 0x401C50: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (in /tmp/a.out) ==22370== by 0x401BCD: std::thread::_Impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (in /tmp/a.out) ==22370== by 0x5112D1F: execute_native_thread_routine (in /usr/lib/libstdc++.so.6.0.20) ==22370== by 0x4C30466: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) ==22370== Address 0xffefffbbf is on thread #1's stack ==22370== in frame #2, created by main (???) ==22370== 0 ==22370== ==22370== For counts of detected and suppressed errors, rerun with: -v ==22370== Use --history-level=approx or =none to gain increased speed, at ==22370== the cost of reduced accuracy of conflicting-access information ==22370== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 25 from 25)
I think that this just needs a specific suppression, something like { helgrind---std::atomic assignment Helgrind:Race fun:store fun:_ZNSt13__atomic_baseIbEaSEb fun:_ZNSt6atomicIbEaSEb }
That's not enough, at least for Fedora 33, where I get further hazards ==12068== Possible data race during write of size 1 at 0x4DC7E30 by thread #1 ==12068== Locks held: none ==12068== at 0x4846952: mempcpy (vg_replace_strmem.c:1562) ==12068== by 0x4C68261: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C53018: __vfprintf_internal (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C3F2AE: printf (in /usr/lib64/libc-2.32.so) ==12068== by 0x40125E: main (std_atomic.cpp:12) ==12068== Address 0x4dc7e30 is 0 bytes inside a block of size 1,024 alloc'd ==12068== at 0x483B7A5: malloc (vg_replace_malloc.c:307) ==12068== by 0x4C5B6E3: _IO_file_doallocate (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C6A09F: _IO_doallocbuf (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C69237: _IO_file_overflow@@GLIBC_2.2.5 (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C682E5: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C53018: __vfprintf_internal (in /usr/lib64/libc-2.32.so) ==12068== by 0x4C3F2AE: printf (in /usr/lib64/libc-2.32.so) ==12068== by 0x4011FD: main::{lambda()#1}::operator()() const (std_atomic.cpp:9) ==12068== by 0x4015D1: void std::__invoke_impl<void, main::{lambda()#1}>(std::__invoke_other, main::{lambda()#1}&&) (invoke.h:60) ==12068== by 0x401586: std::__invoke_result<main::{lambda()#1}>::type std::__invoke<main::{lambda()#1}>(std::__invoke_result&&, (main::{lambda()#1}&&)...) (invoke.h:95) ==12068== by 0x401533: void std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (thread:264) ==12068== by 0x401507: std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::operator()() (thread:271) ==12068== Block was alloc'd by thread #2
I just tried this on FreeBSD 13 with clang 11 and gcc 10.3 and there were no errors. That's probably because the libc implementation has mutex protection, though I don't understand why I don't see any std::base<bool>::operator=(bool) hazards. I'll recheck on Fedora 34 that the second hazard I saw really came from the std::atomic.
I don't think that the 2nd hazard that I saw was related. It should be suppressed by { helgrind-glibc2X-004 Helgrind:Race obj:@GLIBC_LIBC_PATH@ } (Note that this is a super broad suppression which suppresses every leaf function in glibc. Probably most are thread safe but POSIX doesn't require all to be https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_01) I guess that I was seeing that hazard before the suppression file got made glibc path agnostic. As far as I know printf is thread safe. I just tried the original example again (RHEL 7.9, GCC 11.2) and get no error. Getting back to my original idea of using a suppression. That's not so bad but it will only work with debug builds / builds with -fno-inline. In optimized builds "std::atomic<bool>::operator=(bool)" gets inlined.
This problem is not easily solved "just" by suppressions: std::atomic accesses don't create happens-before edges, and thus spurious data races are also reported for user code: #include <thread> #include <memory> #include <atomic> using namespace std; int main(void) { std::atomic<std::shared_ptr<int>> value; thread reader([&value](){ while(true) { auto ptr = value.load(); if(ptr && *ptr == 1) { // line 13 break; } } }); value.store(make_shared<int>(1)); reader.join(); return 0; } compiled with g++-14 --std=c++20 -ggdb example.c++ and then valgrind --tool=helgrind a.out reports apart from a lot of internals of std::atomic<std::shared_ptr<...>> also ==1512249== Possible data race during read of size 4 at 0x4DF0250 by thread #2 ==1512249== Locks held: none ==1512249== at 0x10921E: main::{lambda()#1}::operator()() const (example.c++:13) ==1512249== by 0x109673: void std::__invoke_impl<void, main::{lambda()#1}>(std::__invoke_other, main::{lambda()#1}&&) (invoke.h:61) ==1512249== by 0x109636: std::__invoke_result<main::{lambda()#1}>::type std::__invoke<main::{lambda()#1}>(main::{lambda()#1}&&) (invoke.h:96) ==1512249== by 0x1095E3: void std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (std_thread.h:301) ==1512249== by 0x1095B7: std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::operator()() (std_thread.h:308) ==1512249== by 0x10959B: std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda()#1}> > >::_M_run() (std_thread.h:253) ==1512249== by 0x4964223: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33) ==1512249== by 0x484F38A: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1512249== by 0x4B9F39B: start_thread (pthread_create.c:444) ==1512249== by 0x4C2048F: clone (clone.S:100) ==1512249== Address 0x4df0250 is 16 bytes inside a block of size 24 alloc'd ==1512249== at 0x4842FD3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1512249== by 0x10A986: std::__new_allocator<std::_Sp_counted_ptr_inplace<int, std::allocator<void>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:151) ==1512249== by 0x10A59B: allocate (allocator.h:196) ==1512249== by 0x10A59B: allocate (alloc_traits.h:515) ==1512249== by 0x10A59B: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<int, std::allocator<void>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<int, std::allocator<void>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<int, std::allocator<void>, (__gnu_cxx::_Lock_policy)2> >&) (allocated_ptr.h:98) ==1512249== by 0x10A3FB: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<int, std::allocator<void>, int>(int*&, std::_Sp_alloc_shared_tag<std::allocator<void> >, int&&) (shared_ptr_base.h:967) ==1512249== by 0x10A22F: std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void> >, int&&) (shared_ptr_base.h:1713) ==1512249== by 0x109EEE: std::shared_ptr<int>::shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void> >, int&&) (shared_ptr.h:463) ==1512249== by 0x109BF5: std::shared_ptr<int> std::make_shared<int, int>(int&&) (shared_ptr.h:1008) ==1512249== by 0x1092A8: main (example.c++:19) ==1512249== Block was alloc'd by thread #1 ==1512249==
I've long given up using suppressions. I can't see how this can be fixed in Valgrind. There is instrumentation, but in this case I think that it would need to be done within the standard library or at least by hacking into the internals of std::atomic/std::shared_ptr.