После создания потока myThreadпоток mainпродолжает свою работу, по крайней мере на мгновение, пока не достигнет следующей строки.
boost::thread::yield();
Это переводит текущий поток (в данном случае поток main) в неактивное состояние, что означает переключение операционной системы на другой поток или процесс, используя некоторую политику, которая зависит от операционной системы. С помощью функции yieldоперационная система уведомляется о том, что текущий поток хочет уступить оставшуюся часть кванта времени. В это время новый поток выполняет threadFun. После завершения threadFunдочерний поток исчезает. Следует отметить, что объект threadне уничтожается, потому что он является объектом С++, который по-прежнему находится в области видимости. Эта особенность играет важную роль.
Объект потока — это некий объект, существующий в динамической памяти или в стеке и работающий подобно любому другому объекту С++. Когда программный код выходит из области видимости потока, все находящиеся в стеке объекты потока уничтожаются, или, с другой стороны, когда вызывающая программа выполняет оператор deleteдля thread*, исчезает соответствующий объект thread, который находится в динамической памяти. Но объекты threadвыступают просто как прокси относительно реальных потоков операционной системы, и когда они уничтожаются, потоки операционной системы не обязательно исчезают. Они просто отсоединяются, что означает невозможность их подключения в будущем. Это не так уж плохо.
Потоки используют ресурсы, и в любом (хорошо спроектированном) многопоточном приложении управление доступом к таким ресурсам (к объектам, сокетам, файлам, «сырой» памяти и т.д.) осуществляется при помощи мьютексов, которые являются объектами, обеспечивающими последовательный доступ к каким-либо объектам со стороны нескольких потоков (см. рецепт 12.2). Если поток операционной системы оказывается «убитым», он не будет освобождать свои блокировки и свои ресурсы, подобно тому как «убитый» процесс не оставляет шансов на очистку буферов или правильное освобождение ресурсов операционной системы. Простое завершение потока в тот момент, когда вам кажется, что он должен быть завершен, — это все равно что убрать лестницу из-под маляра, когда время его работы закончилось.
Поэтому предусмотрена функция-член 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.
Читать дальше