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



, то программа завершится (деструктор >std::thread вызовет функцию >std::terminate()). Поэтому вы обязаны гарантировать, что поток корректно присоединен либо отсоединен, даже если возможны исключения. Соответствующая техника программирования описана в разделе 2.1.3. Отметим, что это решение следует принять именно до уничтожения объекта >std::thread, к самому потоку оно не имеет отношения. Поток вполне может завершиться задолго до того, как программа присоединится к нему или отсоединит его. А отсоединенный поток может продолжать работу и после уничтожения объекта >std::thread.

Если вы не хотите дожидаться завершения потока, то должны гарантировать, что данные, к которым поток обращается, остаются действительными до тех пор, пока они могут ему понадобиться. Эта проблема не нова даже в однопоточной программа доступ к уже уничтоженному объекту считается неопределенным поведением, но при использовании потоков есть больше шансов столкнуться с проблемами, обусловленными временем жизни.

Например, такая проблема возникает, если функция потока хранит указатели или ссылки на локальные переменные, и поток еще не завершился, когда произошел выход из области видимости, где эти переменные определены. Соответствующий пример приведен в листинге 2.1.


Листинг 2.1. Функция возвращает управление, когда поток имеет доступ к определенным в ней локальным переменным

>struct func {

> int& i;

> func(int& i_) : i(i_){}

> void operator() () {

>  for(unsigned j = 0; j < 1000000; ++j) {

>   do_something(i); ←┐Потенциальный доступ

>  }                  (1) к висячей ссылке

> }

>};


>void oops() {

> int some_local_state = 0;        (2) He ждем завершения

> func my_func(some_local_state); ←┘потока

> std::thread my_thread(my_func); ←┐Новый поток, возможно,

> my_thread.detach();              (3) еще работает

>}

В данном случае вполне возможно, что новый поток, ассоциированный с объектом >my_thread, будет еще работать, когда функция >oops вернет управление (2), поскольку мы явно решили не дожидаться его завершения, вызвав >detach()(3). А если поток действительно работает, то при следующем вызове >do_something(i)(1) произойдет обращение к уже уничтоженной переменной. Точно так же происходит в обычном однопоточном коде — сохранять указатель или ссылку на локальную переменную после выхода из функции всегда плохо, — но в многопоточном коде такую ошибку сделать проще, потому что не сразу видно, что произошло.

Один из распространенных способов разрешить такую ситуацию — сделать функцию потока замкнутой, то есть