Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 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) произойдет обращение к уже уничтоженной переменной. Точно так же происходит в обычном однопоточном коде — сохранять указатель или ссылку на локальную переменную после выхода из функции всегда плохо, — но в многопоточном коде такую ошибку сделать проще, потому что не сразу видно, что произошло.
Один из распространенных способов разрешить такую ситуацию — сделать функцию потока замкнутой, то есть