线程时访问静态映射时出现隔离错误

Segfault when accessing static map when threading

本文关键字:隔离 错误 映射 访问 静态 线程      更新时间:2023-10-16

请考虑以下代码片段:

myEnum stringToEnum(const std::string& enumString) {
static const std::unordered_map<std::string, myEnum> conversionMap = {
{"enumA", myEnum::enumA}, 
{"enumB", myEnum::enumB},
{"enumC", myEnum::enumC}};
if(conversionMap.count(enumString) == 0) {
// Return some default value
}
return conversionMap.at(enumString);
}

void threadedFcn() {
// Do some things
for (int i = 0; i < someNumber; ++i) {
// Do some more things
auto myEnum = stringToEnum(myString);
// Do even more stuff
}
}
int main() {
threadedFcn(); // No problem
std::thread(threadedFcn).join(); // No problem
std::thread(threadedFcn).detach(); // Seg fault
}

关于这个的一些事情:

  1. 如果我删除对count的调用,那么它不会出错。看起来它是由某种原因引起的。

  2. 它只发生在Linux Debian 9上(我也用Windows 10和Mac构建过(。

  3. 如果我在分离调用后添加类似std::this_thread::sleep_for(std::chrono::milliseconds(2000));的东西,那么它不会出错。

  4. 如果我conversionMap不是静态的,那么它就不会出错。

我无法弄清楚确切的问题是什么,但这与主线程在分离的线程完成之前退出有关。

正如 Slava 正确所说,问题是你让main返回,而你的另一个线程正在运行。

main返回最终调用exit,它会通过所有注册的atexit处理程序。

使用libstdc++(可能还有大多数其他 C++ 运行时实现(时,任何已构造的静态C++对象都会向atexit注册其析构函数,因此在该析构函数触发后,分离的线程将访问已销毁conversionMap对象,结果可预测。

您可以使用地址清理程序 (-fsanitize=address( 来观察这一点,该清理程序报告:

=================================================================
==87625==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000010 at pc 0x563122b9c6c5 bp 0x7fc54eafea20 sp 0x7fc54eafea18
READ of size 8 at 0x603000000010 thread T2
#0 0x563122b9c6c4 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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_begin(unsigned long) const /usr/include/c++/9/bits/hashtable.h:943
#1 0x563122b9bcf2 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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>
>::count(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const /usr/include/c++/9/bits/hashtable.h:1451
#2 0x563122b9b76e in std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, myEnum, 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::ba
sic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > >::count(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const /usr/include/c++/9/bits/unordered_map.h:939
#3 0x563122b9a793 in stringToEnum(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /tmp/t.cc:16
#4 0x563122b9aa9c in threadedFcn() /tmp/t.cc:27
#5 0x563122b9f71e in void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/include/c++/9/bits/invoke.h:60
#6 0x563122b9f681 in std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/include/c++/9/bits/invoke.h:95
#7 0x563122b9f5cb in void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/9/thread:244
#8 0x563122b9f56c in std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/include/c++/9/thread:251
#9 0x563122b9f4ed in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/include/c++/9/thread:195
#10 0x7fc552301baf  (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xcebaf)
#11 0x7fc55202bf26 in start_thread /build/glibc-M65Gwz/glibc-2.30/nptl/pthread_create.c:479
#12 0x7fc5521532ee in __clone (/lib/x86_64-linux-gnu/libc.so.6+0xfd2ee)
0x603000000010 is located 0 bytes inside of 24-byte region [0x603000000010,0x603000000028)
freed by thread T0 here:
#0 0x7fc552509f97 in operator delete(void*) (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x109f97)
#1 0x563122b9e5fb in __gnu_cxx::new_allocator<std::__detail::_Hash_node_base*>::deallocate(std::__detail::_Hash_node_base**, unsigned long) /usr/include/c++/9/ext/new_allocator.h:128
#2 0x563122b9dbf5 in std::allocator_traits<std::allocator<std::__detail::_Hash_node_base*> >::deallocate(std::allocator<std::__detail::_Hash_node_base*>&, std::__detail::_Hash_node_base**, unsigned long) /usr/include/c++/9/bits/alloc_traits.h:470
#3 0x563122b9d0cc in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum>, true> > >::_M_deallocate_buckets(std::__detail::_Hash_node_base**, unsigned long) /usr/include/c++/9/bits/hashtable_policy.h:2148
#4 0x563122b9c5d3 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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_deallocate_buckets(std::__detail::_Hash_node_base**, unsigned long) /usr/include/c++/9/bits/hashtable.h:370
#5 0x563122b9bc99 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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_deallocate_buckets() /usr/include/c++/9/bits/hashtable.h:375
#6 0x563122b9b73b 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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>
>::~_Hashtable() /usr/include/c++/9/bits/hashtable.h:1353
#7 0x563122b9b4ff in std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, myEnum, 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::ba
sic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > >::~unordered_map() /usr/include/c++/9/bits/unordered_map.h:102
#8 0x7fc552093e26 in __run_exit_handlers /build/glibc-M65Gwz/glibc-2.30/stdlib/exit.c:108
previously allocated by thread T0 here:
#0 0x7fc55250919f in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10919f)
#1 0x563122b9e828 in __gnu_cxx::new_allocator<std::__detail::_Hash_node_base*>::allocate(unsigned long, void const*) /usr/include/c++/9/ext/new_allocator.h:114
#2 0x563122b9defc in std::allocator_traits<std::allocator<std::__detail::_Hash_node_base*> >::allocate(std::allocator<std::__detail::_Hash_node_base*>&, unsigned long) /usr/include/c++/9/bits/alloc_traits.h:444
#3 0x563122b9d6d4 in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum>, true> > >::_M_allocate_buckets(unsigned long) /usr/include/c++/9/bits/hashtable_policy.h:2134
#4 0x563122b9ce5b 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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_allocate_buckets(unsigned long) /usr/include/c++/9/bits/hashtable.h:361
#5 0x563122b9c3db 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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>
>::_Hashtable<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> const*>(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> const*, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> const*, unsigned l
ong, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::__detail::_Mod_range_hashing const&, std::__detail::_Default_ranged_hash const&, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::__detail::_Select1st const&, std::allocator<std::pair<std::__cxx
11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > const&) /usr/include/c++/9/bits/hashtable.h:989
#6 0x563122b9bab5 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, myEnum>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, 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>
>::_Hashtable(std::initializer_list<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, unsigned long, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&,
std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > const&) /usr/include/c++/9/bits/hashtable.h:466
#7 0x563122b9b6a3 in std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, myEnum, 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::ba
sic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > >::unordered_map(std::initializer_list<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> >, unsigned long, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::equal_to<std::__
cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, myEnum> > const&) /usr/include/c++/9/bits/unordered_map.h:231
#8 0x563122b9a678 in stringToEnum(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /tmp/t.cc:14
#9 0x563122b9aa9c in threadedFcn() /tmp/t.cc:27
#10 0x563122b9ac0f in main /tmp/t.cc:33
#11 0x7fc55207ce0a in __libc_start_main ../csu/libc-start.c:308
Thread T2 created by T0 here:
#0 0x7fc5524399b2 in pthread_create (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x399b2)
#1 0x7fc552301e24 in std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xcee24)
#2 0x563122b9ac75 in main /tmp/t.cc:35
#3 0x7fc55207ce0a in __libc_start_main ../csu/libc-start.c:308

更新:

我想一个简单的解决方法是让我的变量不是静态的(功能上它会是相同的,但性能明显下降(。

有一种简单的方法可以避免在每次调用时都构造新的unordered_map,同时避免全局销毁问题:使用静态指针:

myEnum stringToEnum(const std::string& enumString) {
static const auto *conversionMap =
new std::unordered_map<std::string, myEnum>{
{"enumA", myEnum::enumA}, 
{"enumB", myEnum::enumB},
{"enumC", myEnum::enumC}
};
if (conversionMap->count(enumString) == 0) {
// Return some default value
}
return conversionMap->at(enumString);
}