2.1.4. Запуск потоков в фоновом режиме

Вызов функции-члeнa detach() объекта std::thread оставляет поток работать в фоновом режиме, без прямых способов коммуникации с ним. Теперь ждать завершения потока не получится — после того как поток отсоединен, уже невозможно получить ссылающийся на него объект std::thread, для которого можно было бы вызвать join(). Отсоединенные потоки действительно работают в фоне: отныне ими владеет и управляет библиотека времени выполнения С++, которая обеспечит корректное освобождение связанных с потоком ресурсов при его завершении.

Отсоединенные потоки часто называют потоками-демонами по аналогии с процессами-демонами в UNIX, то есть с процессами, работающими в фоновом режиме и не имеющими явного интерфейса с пользователем. Обычно такие потоки работают в течение длительного времени, в том числе на протяжении всего времени жизни приложения. Они, например, могут следить за состоянием файловой системы, удалять неиспользуемые записи из кэша или оптимизировать структуры данных. С другой стороны, иногда отсоединенный поток применяется, когда существует какой-то другой способ узнать о его завершении или в случае, когда нужно запустить задачу и «забыть» о ней.

В разделе 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();

  if (cmd.type == open_new_document) {

   std::string const new_name = get_filename_from_user();

   std::thread t(edit_document,new_name); ← (1)

   t.detach(); ← (2)

  }

  else {

   process_user_input(cmd);

  }

 }

}

Когда пользователь открывает новый документ, мы спрашиваем, какой документ открыть, затем запускаем поток, в котором этот документ открывается (1), и отсоединяем его (2). Поскольку новый поток делает то же самое, что текущий, только с другим файлом, то мы можем использовать ту же функцию (edit_document), передав ей в качестве аргумента имя только что выбранного файла.

Этот пример демонстрирует также, почему бывает полезно передавать аргументы функции потока: мы передаем конструктору объекта std::thread не только имя функции (1), но и её параметр — имя файла. Существуют другие способы добиться той же цели, например, использовать не обычную функцию с параметрами, а объект-функцию с данными-членами, но библиотека предлагает и такой простой механизм.