Деструктор класса thread_guard
в листинге 2.3 сначала проверяет, что объект std::thread
находится в состоянии joinable()
(1) и, лишь если это так, вызывает join()
(2). Это существенно, потому что функцию join()
можно вызывать только один раз для данного потока, так что если он уже присоединился, то делать это вторично было бы ошибкой.
Копирующий конструктор и копирующий оператор присваивания помечены признаком =delete
(3), чтобы компилятор не генерировал их автоматически: копирование или присваивание такого объекта таит в себе опасность, поскольку время жизни копии может оказаться дольше, чем время жизни присоединяемого потока. Но раз эти функции объявлены как «удаленные», то любая попытка скопировать объект типа thread_guard
приведет к ошибке компиляции. Дополнительные сведения об удаленных функциях см. в приложении А, раздел А.2.
Если ждать завершения потока не требуется, то от проблемы безопасности относительно исключений можно вообще уйти, отсоединив поток. Тем самым связь потока с объектом std::thread
разрывается, и при уничтожении объекта std::thread
функция std::terminate()
не будет вызвана. Но отсоединенный поток по-прежнему работает — в фоновом режиме.
Вызов функции-члeнa detach()
объекта std::thread
оставляет поток работать в фоновом режиме, без прямых способов коммуникации с ним. Теперь ждать завершения потока не получится — после того как поток отсоединен, уже невозможно получить ссылающийся на него объект std::thread
, для которого можно было бы вызвать join()
. Отсоединенные потоки действительно работают в фоне: отныне ими владеет и управляет библиотека времени выполнения С++, которая обеспечит корректное освобождение связанных с потоком ресурсов при его завершении.
Отсоединенные потоки часто называют
В разделе 2.1.2 мы уже видели, что для отсоединения потока следует вызвать функцию-член detach()
объекта std::thread
. После возврата из этой функции объект std::thread
уже не связан ни с каким потоком, и потому присоединиться к нему невозможно.
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
Разумеется, чтобы отсоединить поток от объекта std::thread
, поток должен существовать: нельзя вызвать detach()
для объекта std::thread
, с которым не связан никакой поток. Это то же самое требование, которое предъявляется к функции join()
, поэтому и проверяется оно точно так же — вызывать t.detach()
для объекта t
типа std::thread
можно только тогда, когда t.joinable()
возвращает true
.
Возьмем в качестве примера текстовый редактор, который умеет редактировать сразу несколько документов. Реализовать его можно разными способами — как на уровне пользовательского интерфейса, так и с точки зрения внутренней организации. В настоящее время все чаще для этой цели используют несколько окон верхнего уровня, по одному для каждого редактируемого документа. Хотя эти окна выглядят совершенно независимыми, в частности, у каждого есть свое меню и все прочее, на самом деле они существуют внутри единственного экземпляра приложения. Один из подходов к внутренней организации программы заключается в том, чтобы запускать каждое окно в отдельном потоке: каждый такой поток исполняет один и тот же код, но с разными данными, описывающими редактируемый документ и соответствующее ему окно. Таким образом, чтобы открыть еще один документ, необходимо создать новый поток. Потоку, обрабатывающему запрос, нет дела до того, когда созданный им поток завершится, потому что он работает над другим, независимым документом. Вот типичная ситуация, когда имеет смысл запускать отсоединенный поток.
В листинге 2.4 приведен набросок кода, реализующего этот подход.
Листинг 2.4. Отсоединение потока для обработки другого документа
void edit_document(std::string const& filename) {
open_document_and_display_gui(filename);
while(!done_editing()) {
user_command cmd = get_user_input();