2 votes

Erreur de segmentation lors de l'accès à une carte statique lors d'un threading

Prenons l'exemple de l'extrait de code suivant :

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
}

Quelques précisions à ce sujet :

  1. Si je supprime l'appel à count alors il n'y aura pas de faute de ségrégation. On dirait qu'il en est la cause d'une manière ou d'une autre.

  2. Cela ne se produit que sur Linux Debian 9 (j'ai construit avec Windows 10 et Mac également).

  3. Si j'ajoute quelque chose comme std::this_thread::sleep_for(std::chrono::milliseconds(2000)); après l'appel au détachement, alors il n'y aura pas de faute de ségrégation.

  4. Si je fais conversionMap pas statique, alors il n'y aura pas de faute de ségrégation.

Je n'arrive pas à comprendre quel est le problème exact, mais cela a quelque chose à voir avec le fait que le thread principal se termine avant que le thread détaché ne se termine.

1voto

Employed Russian Points 50479

Comme Slava l'a dit à juste titre, le problème est que vous laissez les main pendant que l'autre thread est en cours d'exécution.

De retour de main appelle éventuellement exit qui passe en revue tous les atexit les manipulateurs.

Lors de l'utilisation de libstdc++ (et probablement la plupart des autres implémentations du runtime C++), tout objet statique C++ qui a été construit enregistre son destructeur avec l'option atexit Ainsi, après le déclenchement du destructeur, votre thread détaché accède à la destruction. conversionMap avec des résultats prévisibles.

Vous pouvez le constater en utilisant Address Sanitizer ( -fsanitize=address ), qui rapporte :

=================================================================
==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

Mise à jour :

Je suppose qu'une solution simple consiste à rendre ma variable non statique (fonctionnellement, ce sera la même chose, mais avec un impact évident sur les performances).

Il existe un moyen simple d'éviter la construction d'une nouvelle unordered_map à chaque appel, tout en évitant le problème de la destruction globale : utilisez un pointeur statique :

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);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X