среда, 16 марта 2016 г.

[prog.c++11] Да, очень аккуратно нужно обращаться с голыми нитями

Не смотря на то, что с многопоточностью имею дело уже больше 20 лет, при работе с голыми нитями нет-нет, да и нарываешься на неожиданности (а все потому, что когда пользуешься правильными инструментами, нужды работать с голыми потоками и нет). Вот минимальный пример, воспроизводящий проблему, с которой сегодня довелось столкнуться:

#include <thread>
#include <vector>
#include <chrono>
#include <iostream>

using namespace std;
using namespace std::chrono;

void run_threads( const vector< unsigned int > & times )
{
   vector< thread > threads;
   
   forsize_t i = 0; i <= times.size(); ++i )
      threads.emplace_back(
            [t = times.at(i)]{ this_thread::sleep_for( seconds{t} ); } );

   forauto & t : threads )
      t.join();
}

int main()
{
   try
   {
      run_threads( { 1234321 } );
   }
   catchconst std::exception & x )
   {
      std::cerr << x.what() << std::endl;
   }
}

Я тут намеренно спровоцировал выход за пределы вектора, дабы породить исключение. Только вот вместо нормального завершения после перехвата этого исключения программа под GCC/Clang падает с сообщением "terminate called without an active exception". Далеко не сразу удалось въехать, почему же так происходит...

Дело в том, что для уже стартовавших нитей не вызывается join. Что, видимо, и крешит приложение где-то в деструкторе std::thread.

Вот так. Казалось бы, везде объекты с деструкторами, посему и RAII должно быть во все поля... Ан нет: не вызвал join() явно -- получил гранату :) А все почему? А все потому, что не нужно с голыми потоками работать, лучше правильные инструменты использовать, которые берут на себя заботу о целой куче деталей... ;)

Комментариев нет: