Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ стр 9.

Шрифт
Фон

Листинг 2.2. Ожидание завершения потока

struct func; см. определение

              в листинге 2.1

void f() {

 int some_local_state = 0;

 func my_func(some_local_state)

 std::thread t(my_func);

 try {

  do_something_in_current_thread()

 }

 catch(...) {

  t.join(); (1)

  throw;

 }

 t.join();  (2)

}

В листинге 2.2 блок try/catch используется для того, чтобы поток, имеющий доступ к локальному состоянию, гарантированно завершился до выхода из функции вне зависимости оттого, происходит выход нормально (2) или вследствие исключения (1). Записывать блоки try/catch очень долго и при этом легко допустить ошибку, поэтому такой способ не идеален. Если необходимо гарантировать, что поток завершается до выхода из функции потому ли, что он хранит ссылки на локальные переменные, или по какой-то иной причине то важно обеспечить это на всех возможных путях выхода, как нормальных, так и в результате исключения, и хотелось бы иметь для этого простой и лаконичный механизм.

Один из способов решить эту задачу воспользоваться стандартной идиомой захват ресурса есть инициализация (RAII) и написать класс, который вызывает join() в деструкторе, например, такой, как в листинге 2.3. Обратите внимание, насколько проще стала функция f().

Листинг 2.3. Использование идиомы RAII для ожидания завершения потока

class thread_guard {

 std::threads t;

public:

 explicit thread_guard(std::thread& t_) : t(t_) {}

 ~thread_guard() {

  if (t.joinable()) (1)

  {

   t.join();        (2)

  }

 }

 thread_guard(thread_guard const&)=delete; (3)

 thread_guard& operator=(thread_guard const&)=delete;

};

struct func; см.определение

              в листинге 2.1

void f() {

 int some_local_state;

 std::thread t(func(some_local_state));

 thread_guard g(t);

 do_something_in_current_thread();

}             (4)

Когда текущий поток доходит до конца f (4), локальные объекты уничтожаются в порядке, обратном тому, в котором были сконструированы. Следовательно, сначала уничтожается объект g типа thread_guard, и в его деструкторе (2) происходит присоединение к потоку Это справедливо даже в том случае, когда выход из функции f произошел в результате исключения внутри функции do_something_in_current_thread.

Деструктор класса thread_guard в листинге 2.3 сначала проверяет, что объект std::thread находится в состоянии joinable() (1) и, лишь если это так, вызывает join() (2). Это существенно, потому что функцию join() можно вызывать только один раз для данного потока, так что если он уже присоединился, то делать это вторично было бы ошибкой.

Копирующий конструктор и копирующий оператор присваивания помечены признаком =delete (3), чтобы компилятор не генерировал их автоматически: копирование или присваивание такого объекта таит в себе опасность, поскольку время жизни копии может оказаться дольше, чем время жизни присоединяемого потока. Но раз эти функции объявлены как «удаленные», то любая попытка скопировать объект типа thread_guard приведет к ошибке компиляции. Дополнительные сведения об удаленных функциях см. в приложении А, раздел А.2.

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

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 {

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке