кажется, что он должен быть завершен, — это все равно что убрать лестницу из-под маляра, когда время его работы закончилось.
Поэтому предусмотрена функция-член join
. Как показано в примере 12.1, вы можете вызвать join
, чтобы дождаться завершения работы дочернего потока, join
— это вежливый способ уведомления потока, что вы собираетесь ждать завершения его работы.
myThread.join();
Поток, вызвавший функцию join
, переходит в состояние ожидания, пока не закончит свою работу другой поток, представленный объектом myThread
. Если он никогда не завершится, то никогда не завершится и join
. Применение join
— наилучший способ ожидания завершения работы дочернего потока.
Возможно, вы заметили, что, если передать что-либо осмысленное функции threadFun
, но закомментировать join
, поток не завершит свою работу. Вы можете убедиться в этом, выполняя в threadFun
цикл или какую-нибудь продолжительную операцию. Это объясняется тем, что операционная система уничтожает процесс вместе со всеми его дочерними процессами независимо от того, закончили или нет они свою работу. Без вызова join
функция main
не будет ждать окончания работы своих дочерних потоков: она завершается, и поток операционной системы уничтожается.
Если требуется создать несколько потоков, рассмотрите возможность их группирования в объект thread_group
. Объект thread_group
может управлять объектами двумя способами. Во-первых, вы можете вызвать add_thread
с указателем на объект thread
, и этот объект будет добавлен в группу. Ниже приводится пример.
boost::thread_group grp;
boost::thread* p = new boost::thread(threadFun);
grp.add_thread(p);
// выполнить какие-нибудь действия...
grp.remove_thread(p);
При вызове деструктора grp
он удалит оператором delete
каждый указатель потока, который был добавлен в add_thread
. По этой причине вы можете добавлять в thread_group
только указатели объектов потоков, размещённых в динамической памяти. Удаляйте поток путем вызова remove_thread
с передачей адреса объекта потока (remove_thread
находит в группе соответствующий объект потока, сравнивая значения указателей, а не сами объекты). remove_thread
удалит указатель, ссылающийся на этот поток группы, но вам придется все же удалить сам поток с помощью оператора delete
.
Кроме того, вы можете добавить поток в группу, не создавая его непосредственно, а используя для этого вызов функции create_thread
, которая (подобно объекту потока) принимает функтор в качестве аргумента и начинает его выполнение в новом потоке операционной системы. Например, для порождения двух потоков и добавления их в группу сделайте следующее.
boost::thread_group grp;
grp.create_thread(threadFun);
grp.create_thread(threadFun); // Теперь группа grp содержит два потока
grp.join_all(); // Подождать завершения всех потоков
При добавлении потоков в группу при помощи create_thread
или add_thread
вы можете вызвать join_all
для ожидания завершения работы всех потоков группы. Вызов join_all
равносилен вызову join
для каждого потока группы: join_all
возвращает управление после завершения работы всех потоков группы.
Создание объекта потока позволяет начать выполнение отдельного потока. Однако с помощью средств библиотеки Boost Threads это делается обманчиво легко, поэтому необходимо тщательно обдумывать проект. Прочтите остальные рецепты настоящей главы, где даются дополнительные предостережения относительно применения потоков.
Рецепт 12.2.
12.2. Обеспечение потокозащищенности ресурсов
В программе используется несколько потоков и требуется гарантировать невозможность модификации ресурса несколькими потоками одновременно. В целом это называется обеспечением
Используйте класс mutex
, определенный в mutex
для управления параллельным доступом к очереди.
#include <iostream>
#include <boost/thread/thread.hpp>
#include <string>
// Простой класс очереди; в реальной программе вместо него следует
// использовать std::queue
template<typename T>
class Queue {
public:
Queue() {}
~Queue() {}
void enqueue(const T& x) {
// Блокировать мьютекс для этой очереди
boost::mutex::scoped_lock lock(mutex_);
list_.push_back(x);
// scoped_lock автоматически уничтожается (и, следовательно, мьютекс
// разблокируется) при выходе из области видимости
}
T dequeue() {
boost::mutex::scoped_lock lock(mutex_);
if (list_.empty())
throw 'empty!'; // Это приводит к выходу из текущей области
T tmp = list_.front(); // видимости, поэтому блокировка освобождается
list_.pop_front();
return(tmp);
} // Снова при выходе из области видимости мьютекс разблокируется
private:
std::list<T> list_;
boost::mutex mutex_;