我遇到了线程清理器 (TSan) 的一些问题,它们抱怨某些生产代码中的数据竞争,其中 std::packaged_task 通过将它们包装在 std::function 中而被移交给调度程序线程。对于这个问题,我简化了它在生产中的作用,同时触发了 TSan。该实现类似于安东尼威廉姆斯在这个问题中给出的答案(至少这是我的理解):非明显的生命周期问题与 std::promise 和 std::future。
请注意,如果使用 std::promise,则会报告相同的数据竞争。如果一个简单的 int 被抛出/捕获,它也会抱怨。如果在 catch 子句中没有访问异常,则不会发现竞争。如果我省略使用包装 lambda 并简单地将 std::packaged_task 移动到线程,则不会发现任何竞争。
#include <future>
#include <thread>
#include <stdexcept>
#include <string>
void throw_exception_func()
{
throw std::runtime_error("test_exception");
}
int main()
{
for(int i = 0; i<1000; ++i) {
std::packaged_task<void()> packaged_task(throw_exception_func);
std::future<void> task_future = packaged_task.get_future();
const std::function<void()> wrapper_func = [&]{
std::packaged_task<void()> pt = std::packaged_task<void()>(std::move(packaged_task));
pt();
};
std::thread task_thread(wrapper_func);
bool expected_exception = false;
try {
task_future.get();
} catch (const std::runtime_error& e) {
expected_exception = std::string(e.what()).substr(0, 4) == "test"; // Do something with the exception.
}
task_thread.join();
if(!expected_exception) {
throw std::runtime_error("Did not get expected exception");
};
}
return 0;
}
编译时的 TSan 输出clang++-3.6 test_packaged_task.cpp -g3 -std=c++14 -pthread -fsanitize=thread -D_GLIBCXX_DEBUG
:
SUMMARY: ThreadSanitizer: data race ??:0 operator delete(void*)
==================
==================
WARNING: ThreadSanitizer: data race (pid=70669)
Write of size 8 at 0x7d2400004d00 by thread T3:
#0 free <null> (a.out+0x000000463eab)
#1 std::__exception_ptr::exception_ptr::exception_ptr(std::__exception_ptr::exception_ptr const&) <null> (libstdc++.so.6+0x00000008d4c8)
#2 ~_Result /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:611 (a.out+0x0000004e08f7)
#3 std::__future_base::_Result<void>::_M_destroy() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:616 (a.out+0x0000004e084c)
#4 std::__future_base::_Result_base::_Deleter::operator()(std::__future_base::_Result_base*) const /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:198 (a.out+0x0000004d3f3a)
#5 ~unique_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/unique_ptr.h:236 (a.out+0x0000004d3cff)
#6 ~_State_baseV2 /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:313 (a.out+0x0000004d95c3)
#7 ~_Task_state_base /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:564 (a.out+0x0000004d941f)
#8 ~_Task_state /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:567 (a.out+0x0000004d921a)
#9 void __gnu_cxx::new_allocator<int>::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/ext/new_allocator.h:124 (a.out+0x0000004d91b4)
#10 void std::allocator_traits<std::allocator<int> >::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::allocator<int>&, std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/alloc_traits.h:542 (a.out+0x0000004d9140)
#11 std::_Sp_counted_ptr_inplace<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:531 (a.out+0x0000004d8d75)
#12 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:150 (a.out+0x0000004ce7fd)
#13 ~__shared_count /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:659 (a.out+0x0000004ce770)
#14 ~__shared_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:925 (a.out+0x0000004d3de9)
#15 ~shared_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr.h:93 (a.out+0x0000004d3d93)
#16 ~packaged_task /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1504 (a.out+0x0000004cbd90)
#17 operator() /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:20 (a.out+0x0000004cac4b)
#18 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*&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/mutex:738 (a.out+0x0000004dcdeb)
#19 std::__future_base::_State_baseV2::_M_set_result(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>, bool) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:386 (a.out+0x0000004dfee9)
#20 std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1403 (a.out+0x0000004d9d54)
#21 std::packaged_task<void ()>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1547 (a.out+0x0000004d2674)
#22 operator() /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:19 (a.out+0x0000004cac3d)
#23 std::_Function_handler<void (), main::$_0>::_M_invoke(std::_Any_data const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1871 (a.out+0x0000004ca748)
#24 std::function<void ()>::operator()() const /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:2267 (a.out+0x0000004d14b0)
#25 void std::_Bind_simple<std::function<void ()> ()>::_M_invoke<>(std::_Index_tuple<>) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1530 (a.out+0x0000004d1370)
#26 std::_Bind_simple<std::function<void ()> ()>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1520 (a.out+0x0000004d1310)
#27 std::thread::_Impl<std::_Bind_simple<std::function<void ()> ()> >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/thread:115 (a.out+0x0000004d12b9)
#28 std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) <null> (libstdc++.so.6+0x0000000b8c6f)
Previous read of size 8 at 0x7d2400004d00 by main thread:
#0 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:27 (a.out+0x0000004c9e10)
#1 std::future<void>::get() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:846 (a.out+0x0000004cb6b5)
#2 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:25 (a.out+0x0000004c9c9c)
#3 std::future<void>::get() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:846 (a.out+0x0000004cb6b5)
#4 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:25 (a.out+0x0000004c9c9c)
... repeated....
使用 GCC 5.2.1 和 GCC 5.3.1 编译g++ test_packaged_task.cpp -g3 -std=c++14 -pthread -fsanitize=thread -D_GLIBCXX_DEBUG
也会产生竞争,但 TSan 输出略有不同:
==================
WARNING: ThreadSanitizer: data race (pid=75845)
Write of size 8 at 0x7d2400009b30 by thread T5:
#0 free <null> (libtsan.so.0+0x000000025819)
#1 <null> <null> (libstdc++.so.6+0x00000008d4c8)
#2 std::__future_base::_Result<void>::~_Result() /usr/include/c++/5/future:611 (a.out+0x00000040e159)
#3 std::__future_base::_Result<void>::_M_destroy() /usr/include/c++/5/future:616 (a.out+0x0000004058f2)
#4 std::__future_base::_Result_base::_Deleter::operator()(std::__future_base::_Result_base*) const /usr/include/c++/5/future:198 (a.out+0x000000404d2d)
#5 std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::~unique_ptr() /usr/include/c++/5/bits/unique_ptr.h:236 (a.out+0x000000406042)
#6 std::__future_base::_State_baseV2::~_State_baseV2() /usr/include/c++/5/future:313 (a.out+0x00000040daa1)
#7 std::__future_base::_Task_state_base<void ()>::~_Task_state_base() /usr/include/c++/5/future:1360 (a.out+0x00000040db55)
#8 std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>::~_Task_state() /usr/include/c++/5/future:1387 (a.out+0x00000040dfb5)
#9 void __gnu_cxx::new_allocator<int>::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/include/c++/5/ext/new_allocator.h:124 (a.out+0x00000040eced)
#10 void std::allocator_traits<std::allocator<int> >::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::allocator<int>&, std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/include/c++/5/bits/alloc_traits.h:542 (a.out+0x00000040e9dd)
#11 std::_Sp_counted_ptr_inplace<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/include/c++/5/bits/shared_ptr_base.h:531 (a.out+0x00000040e464)
#12 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/5/bits/shared_ptr_base.h:150 (a.out+0x00000040848a)
#13 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/5/bits/shared_ptr_base.h:659 (a.out+0x000000405f1c)
#14 std::__shared_ptr<std::__future_base::_Task_state_base<void ()>, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/5/bits/shared_ptr_base.h:925 (a.out+0x0000004078cd)
#15 std::shared_ptr<std::__future_base::_Task_state_base<void ()> >::~shared_ptr() /usr/include/c++/5/bits/shared_ptr.h:93 (a.out+0x0000004078f9)
#16 std::packaged_task<void ()>::~packaged_task() /usr/include/c++/5/future:1504 (a.out+0x0000004079bb)
#17 main::{lambda()#1}::operator()() const <null> (a.out+0x000000403814)
#18 _M_invoke /usr/include/c++/5/functional:1871 (a.out+0x000000403cda)
#19 std::function<void ()>::operator()() const /usr/include/c++/5/functional:2267 (a.out+0x00000040f630)
#20 void std::_Bind_simple<std::function<void ()> ()>::_M_invoke<>(std::_Index_tuple<>) /usr/include/c++/5/functional:1531 (a.out+0x00000040f254)
#21 std::_Bind_simple<std::function<void ()> ()>::operator()() /usr/include/c++/5/functional:1520 (a.out+0x00000040ec1e)
#22 std::thread::_Impl<std::_Bind_simple<std::function<void ()> ()> >::_M_run() /usr/include/c++/5/thread:115 (a.out+0x00000040e8e8)
#23 <null> <null> (libstdc++.so.6+0x0000000b8c6f)
Previous read of size 8 at 0x7d2400009b30 by main thread:
#0 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build/test_packaged_task.cpp:27 (a.out+0x0000004039f8)
Thread T5 (tid=76123, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x000000027577)
#1 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) <null> (libstdc++.so.6+0x0000000b8db2)
#2 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build/test_packaged_task.cpp:22 (a.out+0x0000004038f5)
SUMMARY: ThreadSanitizer: data race ??:0 __interceptor_free
==================
任何人都可以帮忙吗?这是 TSan 的误报吗?