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