ThreadSanitizer工具简单使用(基于ros程序)

前言

涉及多线程编程的程序一不小心就会因为对共享资源不当访问触发数据竞争。所谓的不当访问即是指:多个线程访问同时同一段内存并且至少存在一个线程进行写操作。
数据竞争通常是需要主动避免的,但在程序设计阶段就做到完全避免无疑是一种挑战。
谷歌开发的TreadSanitizer工具(下文写作TSAN)可以帮助我们检测程序运行时是否存在数据竞争。当然,这么强大的工具肯定不仅关注数据竞争,还有诸如死锁之类的问题也会被检测。

一、工具启用

clang 3.2 以及 gcc 4.8 版本开始,该工具就被内置,通过一些编译命令可以启用。
在CmakeList中添加以下代码:

//前略
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
if(NOT ENABLE_TSAN) 
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-sanitize=thread")
endif()
add_executable(cam_recorder_v1
    src/CamRecorderExample.cpp
    src/NodeBase.cpp
    src/ThreadManager.cpp
    src/utils.cpp
    src/CamBase.cpp
    src/CamRecorder.cpp
)
if(ENABLE_TSAN)
  target_compile_options(cam_recorder_v1 PRIVATE -fsanitize=thread)
  set_target_properties(cam_recorder_v1 PROPERTIES LINK_FLAGS -fsanitize=thread)
endif()
//后略

解释:

  • 我的可执行文件叫做cam_recorder_v1
  • 使用option的目的在于快捷控制是否启用TSAN
  • if(NOT ENABLE_TSAN)… 用于在编译模式下主动禁用TSAN。(实测这里不加可能无法关闭TSAN)

编译时命令如下:

catkin_make -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_TSAN=ON

解释:

  • 我使用的catkin_make。非ros程序则将catkin_make换成make
  • -DCMAKE_BUILD_TYPE=RelWithDebInfo用于在启用TSAN的同时保留调试信息,可不加

由于TSAN工具输出的信息可能会很长,而且可能会和程序输出混合,所以将其输出到log文件中,在终端内:

export TSAN_OPTIONS="log_path=tsan_output.log"

编译完成之后,直接运行程序即可。相关信息都会输出到上面的log文件内,该文件会存放在项目文件夹根目录里面。

二、LOG解读

LOG由多个被双横线隔开的段组成,每个段包含了一个被检测出来的问题或者警告,所有能够被检测出来的问题见表: ThreadSanitizerDetectableBugs
我这里主要关心数据竞争,也就是data race。
下面取一个描述数据竞争的log段进行简单解读

==================
WARNING: ThreadSanitizer: data race (pid=63934)
  Read of size 8 at 0x7ffe36488960 by main thread:
    #0 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_bucket_index(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const <null> (cam_recorder_v1+0x694e6)
    #1 std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x679eb)
    #2 std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, ros::Time, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> > >::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x659bf)
    #3 NodeBase::checkTopics() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:82 (cam_recorder_v1+0x5ea8d)
    #4 CamRecorder::run() /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorder.cpp:122 (cam_recorder_v1+0x87d59)
    #5 main /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorderExample.cpp:26 (cam_recorder_v1+0x558a4)

  Previous write of size 8 at 0x7ffe36488960 by thread T5:
    #0 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_rehash_aux(unsigned long, std::integral_constant<bool, true>) <null> (cam_recorder_v1+0x6c61a)
    #1 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_rehash(unsigned long, unsigned long const&) <null> (cam_recorder_v1+0x6b168)
    #2 std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, true>*, unsigned long) <null> (cam_recorder_v1+0x69844)
    #3 std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x67a62)
    #4 std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, ros::Time, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> > >::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x659bf)
    #5 NodeBase::checkTopicsAlive() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:106 (cam_recorder_v1+0x5ee96)
    #6 void std::__invoke_impl<void, void (NodeBase::*&)(), NodeBase*&>(std::__invoke_memfun_deref, void (NodeBase::*&)(), NodeBase*&) <null> (cam_recorder_v1+0x6d262)
    #7 std::__invoke_result<void (NodeBase::*&)(), NodeBase*&>::type std::__invoke<void (NodeBase::*&)(), NodeBase*&>(void (NodeBase::*&)(), NodeBase*&) <null> (cam_recorder_v1+0x6c7db)
    #8 void std::_Bind<void (NodeBase::*(NodeBase*))()>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) <null> (cam_recorder_v1+0x6b5bf)
    #9 void std::_Bind<void (NodeBase::*(NodeBase*))()>::operator()<, void>() <null> (cam_recorder_v1+0x69bf5)
    #10 std::_Function_handler<void (), std::_Bind<void (NodeBase::*(NodeBase*))()> >::_M_invoke(std::_Any_data const&) <null> (cam_recorder_v1+0x67cda)
    #11 std::function<void ()>::operator()() const /usr/include/c++/9/bits/std_function.h:688 (cam_recorder_v1+0x6fabe)
    #12 ManagedThread::threadLoop() /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:76 (cam_recorder_v1+0x6e703)
    #13 void std::__invoke_impl<void, void (ManagedThread::*)(), ManagedThread*>(std::__invoke_memfun_deref, void (ManagedThread::*&&)(), ManagedThread*&&) /usr/include/c++/9/bits/invoke.h:73 (cam_recorder_v1+0x74095)
    #14 std::__invoke_result<void (ManagedThread::*)(), ManagedThread*>::type std::__invoke<void (ManagedThread::*)(), ManagedThread*>(void (ManagedThread::*&&)(), ManagedThread*&&) /usr/include/c++/9/bits/invoke.h:95 (cam_recorder_v1+0x73f28)
    #15 void std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) /usr/include/c++/9/thread:244 (cam_recorder_v1+0x73e12)
    #16 std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> >::operator()() /usr/include/c++/9/thread:251 (cam_recorder_v1+0x73d3f)
    #17 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (ManagedThread::*)(), ManagedThread*> > >::_M_run() /usr/include/c++/9/thread:195 (cam_recorder_v1+0x73c6a)
    #18 <null> <null> (libstdc++.so.6+0xd6de3)

  Location is stack of main thread.

  Location is global '<null>' at 0x000000000000 ([stack]+0x00000001e960)

  Thread T5 (tid=63951, running) created by main thread at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x5ea79)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8)
    #2 ManagedThread::ManagedThread(std::function<void ()>, int) /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:16 (cam_recorder_v1+0x6deb5)
    #3 void __gnu_cxx::new_allocator<ManagedThread>::construct<ManagedThread, std::function<void ()>, int&>(ManagedThread*, std::function<void ()>&&, int&) /usr/include/c++/9/ext/new_allocator.h:146 (cam_recorder_v1+0x7386f)
    #4 void std::allocator_traits<std::allocator<ManagedThread> >::construct<ManagedThread, std::function<void ()>, int&>(std::allocator<ManagedThread>&, ManagedThread*, std::function<void ()>&&, int&) /usr/include/c++/9/bits/alloc_traits.h:483 (cam_recorder_v1+0x7348e)
    #5 std::_Sp_counted_ptr_inplace<ManagedThread, std::allocator<ManagedThread>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<std::function<void ()>, int&>(std::allocator<ManagedThread>, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:548 (cam_recorder_v1+0x72f39)
    #6 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<ManagedThread, std::allocator<ManagedThread>, std::function<void ()>, int&>(ManagedThread*&, std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:679 (cam_recorder_v1+0x727ee)
    #7 std::__shared_ptr<ManagedThread, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<ManagedThread>, std::function<void ()>, int&>(std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr_base.h:1344 (cam_recorder_v1+0x722f3)
    #8 std::shared_ptr<ManagedThread>::shared_ptr<std::allocator<ManagedThread>, std::function<void ()>, int&>(std::_Sp_alloc_shared_tag<std::allocator<ManagedThread> >, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:359 (cam_recorder_v1+0x71b92)
    #9 std::shared_ptr<ManagedThread> std::allocate_shared<ManagedThread, std::allocator<ManagedThread>, std::function<void ()>, int&>(std::allocator<ManagedThread> const&, std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:702 (cam_recorder_v1+0x70e89)
    #10 std::shared_ptr<ManagedThread> std::make_shared<ManagedThread, std::function<void ()>, int&>(std::function<void ()>&&, int&) /usr/include/c++/9/bits/shared_ptr.h:718 (cam_recorder_v1+0x702c5)
    #11 ThreadManager::addThread(std::function<void ()>, int) /home/jzd/get_gazedate_ws/src/capture_master/src/ThreadManager.cpp:98 (cam_recorder_v1+0x6eb1d)
    #12 NodeBase::buildCheckTopicsAliveThread() /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:121 (cam_recorder_v1+0x5f534)
    #13 NodeBase::NodeBase(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/jzd/get_gazedate_ws/src/capture_master/src/NodeBase.cpp:15 (cam_recorder_v1+0x5d9d6)
    #14 CamBase::CamBase(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x595e3)
    #15 CamRecorder::CamRecorder(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) <null> (cam_recorder_v1+0x597ff)
    #16 main /home/jzd/get_gazedate_ws/src/capture_master/src/CamRecorderExample.cpp:20 (cam_recorder_v1+0x55881)

SUMMARY: ThreadSanitizer: data race (/home/jzd/get_gazedate_ws/devel/lib/capture_master/cam_recorder_v1+0x694e6) in std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, ros::Time> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_bucket_index(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const
==================

个人认为一些关键的信息段:

1. WARNING: ThreadSanitizer: data race (pid=63934)
对问题的描述,指明问题类别为数据竞争
2. Read of size 8 at 0x7ffe36488960 by main thread:
read进程的函数调用栈(一般read进程是main进程)
3. Previous write of size 8 at 0x7ffe36488960 by thread T5:
write进程的函数调用栈
4. Thread T5 (tid=63951, running) created by main thread at:
write进程何时被main进程创立

在简单使用中通过函数调用栈找到对应的代码段就能发现问题了。

其他

TSAN会占用程序运行性能,所以如果不需要使用了记得关掉(这也是为什么我在CMAKELIST中要用OPTION,这样不加ON就会自动关掉)
更多参考: ThreadSanitizerCppManual

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值