Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 25



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

Чтобы приложение не завершилось аварийно при возникновении исключения, необходимо решить, что делать в этом случае. Вообще говоря, если вы намеревались вызвать функцию >join() при нормальном выполнении программы, то следует вызывать ее и в случае исключения, чтобы избежать проблем, связанных с временем жизни. В листинге 2.2 приведен простой способ решения этой задачи.


Листинг 2.2. Ожидание завершения потока

>struct func; ←┐см. определение

>              │в листинге 2.1

>void f() {

> int some_local_state = 0;

> func my_func(some_local_state)

> std::thread t(my_func);

> try {

>  do_something_in_current_thread()

> }

> catch(...) {

>  t.join(); ←(1)

>  throw;

> }

> t.join();  ←(2)

>}

В листинге 2.2 блок >try/>catch используется для того, чтобы поток, имеющий доступ к локальному состоянию, гарантированно завершился до выхода из функции вне зависимости оттого, происходит выход нормально (2) или вследствие исключения (1). Записывать блоки >try/>catch очень долго и при этом легко допустить ошибку, поэтому такой способ не идеален. Если необходимо гарантировать, что поток завершается до выхода из функции потому ли, что он хранит ссылки на локальные переменные, или по какой-то иной причине то важно обеспечить это на всех возможных путях выхода, как нормальных, так и в результате исключения, и хотелось бы иметь для этого простой и лаконичный механизм.

Один из способов решить эту задачу воспользоваться стандартной идиомой захват ресурса есть инициализация (RAII) и написать класс, который вызывает >join() в деструкторе, например, такой, как в листинге 2.3. Обратите внимание, насколько проще стала функция >f().


Листинг 2.3. Использование идиомы RAII для ожидания завершения потока

>class thread_guard {

> std::threads t;

>public:

> explicit thread_guard(std::thread& t_) : t(t_) {}

> ~thread_guard() {

>  if (t.joinable()) ←(1)

>  {

>   t.join();        ←(2)

>  }

> }

> thread_guard(thread_guard const&)=delete; ←(3)

> thread_guard& operator=(thread_guard const&)=delete;

>};


>struct func; ←┐см.определение

>              │в листинге 2.1

>void f() {

> int some_local_state;

> std::thread t(func(some_local_state));

> thread_guard g(t);

> do_something_in_current_thread();

>}             ←(4)

Когда текущий поток доходит до конца >f(4), локальные объекты уничтожаются в порядке, обратном тому, в котором были сконструированы. Следовательно, сначала уничтожается объект