Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
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), но и её параметр — имя файла. Существуют другие способы добиться той же цели, например, использовать не обычную функцию с параметрами, а объект-функцию с данными-членами, но библиотека предлагает и такой простой механизм.
2.2. Передача аргументов функции потока
Из листинга 2.4 видно, что по существу передача аргументов вызываемому объекту или функции сводится просто к передаче дополнительных аргументов конструктору std::thread
. Однако важно иметь в виду, что по умолчанию эти аргументы копируются в память объекта, где они доступны вновь созданному потоку, причем так происходит даже в том случае, когда функция ожидает на месте соответствующего параметра ссылку. Вот простой пример:
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
Здесь создается новый ассоциированный с объектом t
поток, в котором вызывается функция f(3, "hello")
. Отметим, что функция f
принимает в качестве второго параметра объект типа std::string
, но мы передаем строковый литерал char const*
, который преобразуется к типу std::string
уже в контексте нового потока. Это особенно важно, когда переданный аргумент является указателем на автоматическую переменную, как в примере ниже:
void f(int i, std::string const& s);
void oops(int some_param) {
char buffer[1024]; ←
(1)
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, buffer);←
(2)
t.detach();
}
В данном случае в новый поток передается (2)указатель на локальную переменную buffer
(1), и есть все шансы, что выход из функции oops произойдет раньше, чем буфер будет преобразован к типу std::string
в новом потоке. В таком случае мы получим неопределенное поведение. Решение заключается в том, чтобы выполнить преобразование в std::string
до передачи buffer
конструктору std::thread
:
void f(int i,std::string const& s);
void not_oops(int some_param) {
char buffer[1024]; │
Использование
sprintf(buffer, "%i", some_param); │
std::string
std::thread t(f, 3, std::string(buffer));←┘
позволяет избежать
t.detach();
висячего указателя
}
В данном случае проблема была в том, что мы положились на неявное преобразование указателя на buffer
к ожидаемому типу первого параметра std::string
, а конструктор std::thread
копирует переданные значения «как есть», без преобразования к ожидаемому типу аргумента.
Возможен и обратный сценарий: копируется весь объект, а вы хотели бы получить ссылку Такое бывает, когда поток обновляет структуру данных, переданную по ссылке, например:
Читать дальшеИнтервал:
Закладка: