I am working on writing a multithreaded application and it currently has a class that spawns threads and then calls its own member functions from within those threads. I thought this would be fine as long as the member function itself doesn't use any shared resources, but when I ran valgrind with the --tool=helgrind option to detect data races and other thread related issues it complains.
foo_main.cpp:
#include <iostream>
#include <mutex>
#include <thread>
class Foo final
{
public:
void DoSomething() const
{
std::thread t1([&] {
// Do I need to lock before accessing "this"?
// std::lock_guard<std::mutex> lock(m_mutex);
this->Print();
});
std::thread t2([&] {
// Do I need to lock before accessing "this"?
// std::lock_guard<std::mutex> lock(m_mutex);
this->Print();
});
t1.join();
t2.join();
}
private:
void Print() const
{
std::lock_guard<std::mutex> lock(m_mutex);
std::cout << "TEST" << std::endl;
}
mutable std::mutex m_mutex;
};
int main()
{
Foo foo;
foo.DoSomething();
}
Here is the output from valgrind:
valgrind --tool=helgrind ./foo
==27751== Helgrind, a thread error detector
==27751== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==27751== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==27751== Command: ./foo
==27751==
TEST
==27751== ---Thread-Announcement------------------------------------------
==27751==
==27751== Thread #1 is the program's root thread
==27751==
==27751== ---Thread-Announcement------------------------------------------
==27751==
==27751== Thread #2 was created
==27751== at 0x7848ACE: clone (in /usr/lib64/libc-2.17.so)
==27751== by 0x5051059: do_clone.constprop.4 (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x5052569: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x4C355BE: pthread_create_WRK (hg_intercepts.c:445)
==27751== by 0x4C36A9B: pthread_create@* (hg_intercepts.c:478)
==27751== by 0x531B596: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x401C94: thread<Foo::DoSomething() const::__lambda0> (thread:135)
==27751== by 0x401C94: Foo::DoSomething() const (foo_main.cpp:14)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== ----------------------------------------------------------------
==27751==
==27751== Possible data race during write of size 8 at 0xA6A6E18 by thread #1
==27751== Locks held: none
==27751== at 0x4018FA: ~_Impl_base (thread:97)
==27751== by 0x4018FA: std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> >::~_Impl() (thread:107)
==27751== by 0x401508: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (new_allocator.h:124)
==27751== by 0x401508: _S_destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (alloc_traits.h:281)
==27751== by 0x401508: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (alloc_traits.h:405)
==27751== by 0x401508: std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:407)
==27751== by 0x401CD0: _M_release (shared_ptr_base.h:144)
==27751== by 0x401CD0: ~__shared_count (shared_ptr_base.h:546)
==27751== by 0x401CD0: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x401CD0: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x401CD0: thread<Foo::DoSomething() const::__lambda0> (thread:135)
==27751== by 0x401CD0: Foo::DoSomething() const (foo_main.cpp:14)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== This conflicts with a previous read of size 8 by thread #2
==27751== Locks held: none
==27751== at 0x531B316: ??? (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x4C357B2: mythread_wrapper (hg_intercepts.c:406)
==27751== by 0x5051EA4: start_thread (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x7848B0C: clone (in /usr/lib64/libc-2.17.so)
==27751== Address 0xa6a6e18 is 24 bytes inside a block of size 56 alloc'd
==27751== at 0x4C2D7B8: operator new(unsigned long) (vg_replace_malloc.c:422)
==27751== by 0x401BEF: allocate (new_allocator.h:104)
==27751== by 0x401BEF: allocate (alloc_traits.h:351)
==27751== by 0x401BEF: __shared_count<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr_base.h:499)
==27751== by 0x401BEF: __shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr_base.h:957)
==27751== by 0x401BEF: shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:316)
==27751== by 0x401BEF: allocate_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:598)
==27751== by 0x401BEF: make_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:614)
==27751== by 0x401BEF: _M_make_routine<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (thread:193)
==27751== by 0x401BEF: thread<Foo::DoSomething() const::__lambda0> (thread:135)
==27751== by 0x401BEF: Foo::DoSomething() const (foo_main.cpp:14)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751== Block was alloc'd by thread #1
==27751==
==27751== ----------------------------------------------------------------
==27751==
==27751== Possible data race during read of size 8 at 0xA6A6E28 by thread #1
==27751== Locks held: none
==27751== at 0x4018FD: ~__shared_count (shared_ptr_base.h:545)
==27751== by 0x4018FD: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x4018FD: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x4018FD: ~_Impl_base (thread:97)
==27751== by 0x4018FD: std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> >::~_Impl() (thread:107)
==27751== by 0x401508: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (new_allocator.h:124)
==27751== by 0x401508: _S_destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (alloc_traits.h:281)
==27751== by 0x401508: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > > (alloc_traits.h:405)
==27751== by 0x401508: std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#1} ()> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:407)
==27751== by 0x401CD0: _M_release (shared_ptr_base.h:144)
==27751== by 0x401CD0: ~__shared_count (shared_ptr_base.h:546)
==27751== by 0x401CD0: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x401CD0: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x401CD0: thread<Foo::DoSomething() const::__lambda0> (thread:135)
==27751== by 0x401CD0: Foo::DoSomething() const (foo_main.cpp:14)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== This conflicts with a previous write of size 8 by thread #2
==27751== Locks held: none
==27751== at 0x531B325: ??? (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x4C357B2: mythread_wrapper (hg_intercepts.c:406)
==27751== by 0x5051EA4: start_thread (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x7848B0C: clone (in /usr/lib64/libc-2.17.so)
==27751== Address 0xa6a6e28 is 40 bytes inside a block of size 56 alloc'd
==27751== at 0x4C2D7B8: operator new(unsigned long) (vg_replace_malloc.c:422)
==27751== by 0x401BEF: allocate (new_allocator.h:104)
==27751== by 0x401BEF: allocate (alloc_traits.h:351)
==27751== by 0x401BEF: __shared_count<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr_base.h:499)
==27751== by 0x401BEF: __shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr_base.h:957)
==27751== by 0x401BEF: shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:316)
==27751== by 0x401BEF: allocate_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:598)
==27751== by 0x401BEF: make_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> >, std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (shared_ptr.h:614)
==27751== by 0x401BEF: _M_make_routine<std::_Bind_simple<Foo::DoSomething() const::__lambda0()> > (thread:193)
==27751== by 0x401BEF: thread<Foo::DoSomething() const::__lambda0> (thread:135)
==27751== by 0x401BEF: Foo::DoSomething() const (foo_main.cpp:14)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751== Block was alloc'd by thread #1
==27751==
TEST
==27751== ---Thread-Announcement------------------------------------------
==27751==
==27751== Thread #3 was created
==27751== at 0x7848ACE: clone (in /usr/lib64/libc-2.17.so)
==27751== by 0x5051059: do_clone.constprop.4 (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x5052569: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x4C355BE: pthread_create_WRK (hg_intercepts.c:445)
==27751== by 0x4C36A9B: pthread_create@* (hg_intercepts.c:478)
==27751== by 0x531B596: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x401E96: thread<Foo::DoSomething() const::__lambda1> (thread:135)
==27751== by 0x401E96: Foo::DoSomething() const (foo_main.cpp:20)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== ----------------------------------------------------------------
==27751==
==27751== Possible data race during write of size 8 at 0xA6A6E18 by thread #1
==27751== Locks held: none
==27751== at 0x40187C: ~_Impl_base (thread:97)
==27751== by 0x40187C: std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> >::~_Impl() (thread:107)
==27751== by 0x4014E6: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (new_allocator.h:124)
==27751== by 0x4014E6: _S_destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (alloc_traits.h:281)
==27751== by 0x4014E6: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (alloc_traits.h:405)
==27751== by 0x4014E6: std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:407)
==27751== by 0x401ED5: _M_release (shared_ptr_base.h:144)
==27751== by 0x401ED5: ~__shared_count (shared_ptr_base.h:546)
==27751== by 0x401ED5: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x401ED5: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x401ED5: thread<Foo::DoSomething() const::__lambda1> (thread:135)
==27751== by 0x401ED5: Foo::DoSomething() const (foo_main.cpp:20)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== This conflicts with a previous read of size 8 by thread #3
==27751== Locks held: none
==27751== at 0x531B316: ??? (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x4C357B2: mythread_wrapper (hg_intercepts.c:406)
==27751== by 0x5051EA4: start_thread (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x7848B0C: clone (in /usr/lib64/libc-2.17.so)
==27751== Address 0xa6a6e18 is 24 bytes inside a block of size 56 alloc'd
==27751== at 0x4C2D7B8: operator new(unsigned long) (vg_replace_malloc.c:422)
==27751== by 0x401DAF: allocate (new_allocator.h:104)
==27751== by 0x401DAF: allocate (alloc_traits.h:351)
==27751== by 0x401DAF: __shared_count<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr_base.h:499)
==27751== by 0x401DAF: __shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr_base.h:957)
==27751== by 0x401DAF: shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:316)
==27751== by 0x401DAF: allocate_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:598)
==27751== by 0x401DAF: make_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:614)
==27751== by 0x401DAF: _M_make_routine<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (thread:193)
==27751== by 0x401DAF: thread<Foo::DoSomething() const::__lambda1> (thread:135)
==27751== by 0x401DAF: Foo::DoSomething() const (foo_main.cpp:20)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751== Block was alloc'd by thread #1
==27751==
==27751== ----------------------------------------------------------------
==27751==
==27751== Possible data race during read of size 8 at 0xA6A6E28 by thread #1
==27751== Locks held: none
==27751== at 0x40187F: ~__shared_count (shared_ptr_base.h:545)
==27751== by 0x40187F: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x40187F: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x40187F: ~_Impl_base (thread:97)
==27751== by 0x40187F: std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> >::~_Impl() (thread:107)
==27751== by 0x4014E6: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (new_allocator.h:124)
==27751== by 0x4014E6: _S_destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (alloc_traits.h:281)
==27751== by 0x4014E6: destroy<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > > (alloc_traits.h:405)
==27751== by 0x4014E6: std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::{lambda()#2} ()> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:407)
==27751== by 0x401ED5: _M_release (shared_ptr_base.h:144)
==27751== by 0x401ED5: ~__shared_count (shared_ptr_base.h:546)
==27751== by 0x401ED5: ~__shared_ptr (shared_ptr_base.h:781)
==27751== by 0x401ED5: ~shared_ptr (shared_ptr.h:93)
==27751== by 0x401ED5: thread<Foo::DoSomething() const::__lambda1> (thread:135)
==27751== by 0x401ED5: Foo::DoSomething() const (foo_main.cpp:20)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751==
==27751== This conflicts with a previous write of size 8 by thread #3
==27751== Locks held: none
==27751== at 0x531B325: ??? (in /usr/lib64/libstdc++.so.6.0.19)
==27751== by 0x4C357B2: mythread_wrapper (hg_intercepts.c:406)
==27751== by 0x5051EA4: start_thread (in /usr/lib64/libpthread-2.17.so)
==27751== by 0x7848B0C: clone (in /usr/lib64/libc-2.17.so)
==27751== Address 0xa6a6e28 is 40 bytes inside a block of size 56 alloc'd
==27751== at 0x4C2D7B8: operator new(unsigned long) (vg_replace_malloc.c:422)
==27751== by 0x401DAF: allocate (new_allocator.h:104)
==27751== by 0x401DAF: allocate (alloc_traits.h:351)
==27751== by 0x401DAF: __shared_count<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr_base.h:499)
==27751== by 0x401DAF: __shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr_base.h:957)
==27751== by 0x401DAF: shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:316)
==27751== by 0x401DAF: allocate_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:598)
==27751== by 0x401DAF: make_shared<std::thread::_Impl<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> >, std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (shared_ptr.h:614)
==27751== by 0x401DAF: _M_make_routine<std::_Bind_simple<Foo::DoSomething() const::__lambda1()> > (thread:193)
==27751== by 0x401DAF: thread<Foo::DoSomething() const::__lambda1> (thread:135)
==27751== by 0x401DAF: Foo::DoSomething() const (foo_main.cpp:20)
==27751== by 0x401480: main (foo_main.cpp:38)
==27751== Block was alloc'd by thread #1
==27751==
==27751==
==27751== Use --history-level=approx or =none to gain increased speed, at
==27751== the cost of reduced accuracy of conflicting-access information
==27751== For lists of detected and suppressed errors, rerun with: -s
==27751== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 9 from 8)
When accessing a member function from multiple threads do I always need to lock BEFORE attempting access through the "this" pointer? Since it is a const member function it won't modify the object itself so I wouldn't think there would be an issue but valgrind is still giving me a lot of warnings.
In the real application I am working on the member function being called by the threads does quite a lot, so moving the code out of the member function and copying it everywhere I use it isn't really an option. But locking before even calling the function poses a problem because a lot of what is in the function could be done concurrently and preventing the portions of the function that don't use shared resources from running concurrently would significantly slow down the application.