The code snippet below is a minimal example of a pattern we use in our production code for doing asynchronous work. When a thread has been spawned, we want to make sure at some point that the work it was meant to do has been completed. We always join threads.
#include <future>
#include <iostream>
#include <thread>
int main(void) {
std::promise<void> p;
std::shared_future<void> f = p.get_future().share();
std::thread t = std::thread([&p] {
p.set_value_at_thread_exit();
std::cout << "Hello thread." << std::endl;
});
f.wait();
std::cout << "Hello main." << std::endl;
t.join();
return 0;
}
The problem I am seeing is that I get warnings from thread sanitizers (both clang and gcc) related to the use of set_value_at_thread_exit()
. If I instead call p.set_value()
at the end of the thread function body, there are no warnings. The output from Clang's ThreadSanitizer running this example is pasted at the end of this post.
Specifically, I want to understand
- Why
set_value_at_thread_exit()
yields a data race whileset_value()
does not? (I also tried the very similar example posted in https://en.cppreference.com/w/cpp/thread/promise/set_value_at_thread_exit; it behaves the same way) - What will the functional difference be comparing the two approaches (given that we always join our threads)? Can we be sure that the work of the thread is complete if we call p.set_value() last thing before returning from the thread? (My guess is that we can)
WARNING: ThreadSanitizer: data race (pid=174249)
Read of size 8 at 0x7b0c00000048 by main thread:
#0 std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::_M_ptr() const <null> (test+0xd4981) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#1 std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::get() const <null> (test+0xd4935) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#2 std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::operator*() const <null> (test+0xd8197) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#3 std::__future_base::_State_baseV2::wait() <null> (test+0xd812b) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#4 std::__basic_future<void>::wait() const <null> (test+0xd177f) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#5 main <null> (test+0xd0dc7) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
Previous write of size 8 at 0x7b0c00000048 by thread T1:
#0 std::enable_if<__and_<std::__not_<std::__is_tuple_like<std::__future_base::_Result_base*>>, std::is_move_constructible<std::__future_base::_Result_base*>, std::is_move_assignable<std::__future_base::_Result_base*>>::value, void>::type std::swap<std::__future_base::_Result_base*>(std::__future_base::_Result_base*&, std::__future_base::_Result_base*&) <null> (test+0xd5145) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#1 std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::swap(std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>&) <null> (test+0xd50ae) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#2 std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::swap(std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>&) <null> (test+0xd48f5) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#3 std::__future_base::_State_baseV2::_M_do_set(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*) <null> (test+0xd5e53) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#4 void std::__invoke_impl<void, void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::__invoke_memfun_deref, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&) <null> (test+0xd6801) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#5 std::__invoke_result<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>::type std::__invoke<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&) <null> (test+0xd66c5) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#6 void std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&)::'lambda'()::operator()() const <null> (test+0xd6640) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#7 std::once_flag::_Prepare_execution::_Prepare_execution<void std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&)::'lambda'()>(void (std::__future_base::_State_baseV2::*&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*))::'lambda'()::operator()() const <null> (test+0xd6595) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#8 std::once_flag::_Prepare_execution::_Prepare_execution<void std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&)::'lambda'()>(void (std::__future_base::_State_baseV2::*&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*))::'lambda'()::__invoke() <null> (test+0xd6525) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#9 pthread_once <null> (test+0x52ffa) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#10 __gthread_once(int*, void (*)()) main.cpp (test+0xd1473) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#11 void std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&) <null> (test+0xd5d68) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#12 std::__future_base::_State_baseV2::_M_set_delayed_result(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>, std::weak_ptr<std::__future_base::_State_baseV2>) <null> (test+0xd5961) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#13 std::promise<void>::set_value_at_thread_exit() <null> (test+0xd5766) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#14 main::$_0::operator()() const main.cpp (test+0xd13d5) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#15 void std::__invoke_impl<void, main::$_0>(std::__invoke_other, main::$_0&&) main.cpp (test+0xd1375) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#16 std::__invoke_result<main::$_0>::type std::__invoke<main::$_0>(main::$_0&&) main.cpp (test+0xd12e5) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#17 void std::thread::_Invoker<std::tuple<main::$_0>>::_M_invoke<0ul>(std::_Index_tuple<0ul>) main.cpp (test+0xd129d) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#18 std::thread::_Invoker<std::tuple<main::$_0>>::operator()() main.cpp (test+0xd1245) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#19 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_0>>>::_M_run() main.cpp (test+0xd1159) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#20 execute_native_thread_routine /build/gcc-12-U8K4Qv/gcc-12-12.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:82:18 (libstdc++.so.6+0xdc3a2) (BuildId: 83485f864cbc36aa1055a1a323e1706a347af35e)
Location is heap block of size 48 at 0x7b0c00000030 allocated by main thread:
#0 operator new(unsigned long) <null> (test+0xcfd86) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#1 std::__new_allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>::allocate(unsigned long, void const*) <null> (test+0xd219e) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#2 std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>::allocate(std::allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&, unsigned long) <null> (test+0xd2059) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#3 std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>> std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>(std::allocator<std::_Sp_counted_ptr_inplace<std::__future_base::_State_baseV2, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&) <null> (test+0xd1d8e) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#4 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<std::__future_base::_State_baseV2, std::allocator<void>>(std::__future_base::_State_baseV2*&, std::_Sp_alloc_shared_tag<std::allocator<void>>) <null> (test+0xd1bed) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#5 std::__shared_ptr<std::__future_base::_State_baseV2, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<void>>(std::_Sp_alloc_shared_tag<std::allocator<void>>) <null> (test+0xd1b4b) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#6 std::shared_ptr<std::__future_base::_State_baseV2>::shared_ptr<std::allocator<void>>(std::_Sp_alloc_shared_tag<std::allocator<void>>) <null> (test+0xd1aaf) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#7 std::shared_ptr<std::enable_if<!is_array<std::__future_base::_State_baseV2>::value, std::__future_base::_State_baseV2>::type> std::make_shared<std::__future_base::_State_baseV2>() <null> (test+0xd1921) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#8 std::promise<void>::promise() <null> (test+0xd1590) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#9 main <null> (test+0xd0d73) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
Thread T1 (tid=174251, finished) created by main thread at:
#0 pthread_create <null> (test+0x4fd6d) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
#1 __gthread_create /build/gcc-12-U8K4Qv/gcc-12-12.2.0/build/x86_64-linux-gnu/libstdc++-v3/include/x86_64-linux-gnu/bits/gthr-default.h:663:35 (libstdc++.so.6+0xdc478) (BuildId: 83485f864cbc36aa1055a1a323e1706a347af35e)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State>>, void (*)()) /build/gcc-12-U8K4Qv/gcc-12-12.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:147:37 (libstdc++.so.6+0xdc478)
#3 main <null> (test+0xd0db9) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d)
SUMMARY: ThreadSanitizer: data race (/home/magnus/slask/test_set_value_at_thread_exit_sanitize/test+0xd4981) (BuildId: 835e6cd1a0a63985f82e88f16af4d885c75ac85d) in std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::_M_ptr() const