Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - [12]

Шрифт
Интервал

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

Один из распространенных способов разрешить такую ситуацию — сделать функцию потока замкнутой, то есть копировать в поток данные, а не разделять их. Если функция потока реализовала в виде вызываемого объекта, то сам этот объект копируется в поток, поэтому исходный объект можно сразу же уничтожить. Однако по-прежнему необходимо следить за тем, чтобы объект не содержал ссылок или указателей, как в листинге 2.1. В частности, не стоит создавать внутри функции поток, имеющий доступ к локальным переменным этой функции, если нет гарантии, что поток завершится до выхода из функции.

Есть и другой способ — явно гарантировать, что поток завершит исполнение до выхода из функции, присоединившись к нему.

2.1.2. Ожидание завершения потока

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

Функция >join() дает очень простую и прямолинейную альтернативу — либо мы ждем завершения потока, либо нет. Если необходим более точный контроль над ожиданием потока, например если необходимо проверить, завершился ли поток, или ждать только ограниченное время, то следует прибегнуть к другим механизмам, таким, как условные переменные и будущие результаты, которые мы будем рассматривать в главе 4. Кроме тот, при вызове >join() очищается вся ассоциированная с потоком память, так что объект >std::thread более не связан с завершившимся потоком — он вообще не связан ни с каким потоком. Это значит, что для каждого потока вызвать функцию >join() можно только один раз; после первого вызова объект >std::thread уже не допускает присоединения, и функция >joinable() возвращает >false.

2.1.3. Ожидание в случае исключения

Выше уже отмечалось, что функцию >join() или >detach() необходимо вызвать до уничтожения объекта >std::thread. Если вы хотите отсоединить поток, то обычно достаточно вызвать >detach() сразу после его запуска, так что здесь проблемы не возникает. Но если вы собираетесь дождаться завершения потока, то надо тщательно выбирать место, куда поместить вызов >join(). Важно, чтобы из-за исключения, произошедшего между запуском потока и вызовом >join(), не оказалось, что обращение к >join() вообще окажется пропущенным.

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


Еще от автора Энтони Д Уильямс
Викиномика. Как массовое сотрудничество изменяет всё

Это знаменитый бестселлер, который научит вас использовать власть массового сотрудничества и покажет, как применять викиномику в вашем бизнесе. Переведенная более чем на двадцать языков и неоднократно номинированная на звание лучшей бизнес-книги, "Викиномика" стала обязательным чтением для деловых людей во всем мире. Она разъясняет, как массовое сотрудничество происходит не только на сайтах Wikipedia и YouTube, но и в традиционных компаниях, использующих технологии для того, чтобы вдохнуть новую жизнь в свои предприятия.Дон Тапскотт и Энтони Уильямс раскрывают принципы викиномики и рассказывают потрясающие истории о том, как массы людей (как за деньги, так и добровольно) создают новости, изучают геном человека, создают ремиксы любимой музыки, находят лекарства от болезней, редактируют школьные учебники, изобретают новую косметику, пишут программное обеспечение и даже строят мотоциклы.Знания, ресурсы и вычислительные способности миллиардов людей самоорганизуются и превращаются в новую значительную коллективную силу, действующую согласованно и управляемую с помощью блогов, вики, чатов, сетей равноправных партнеров и личные трансляции.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.