mutex
в библиотеке Boost Threads. ReadWriteMutex
, т.е. мьютекс с некоторыми дополнительными возможностями.
Наконец, потоки делают что-то одно из трех: работают, находятся в ожидании чего-то или готовы начать работу, но ничего не ожидают и не выполняют никаких действий. Эти состояния носят названия состояний
12.1. Создание потока
Требуется создать поток (thread) для выполнения некоторой задачи, в то время как главный поток продолжает свою работу.
Создайте объект класса thread
и передайте ему функтор, который выполняет данную работу. Создание объекта потока приведет к инстанцированию потока операционной системы, который начинает выполнять оператор operator()
с вашим функтором (или начинает выполнять функцию, переданную с помощью указателя). Пример 12.1 показывает, как это делается.
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
struct MyThreadFunc {
void operator()() {
// Что-нибудь работающее долго...
}
} threadFun;
int main() {
boost::thread myThread(threadFun); // Создать поток, запускающий
// функцию threadFun
boost.:thread::yield(); // Уступить порожденному потоку квант времени.
// чтобы он мог выполнить какую-то работу.
// Выполнить какую-нибудь другую работу
myThread join(); // Текущий поток (т.е поток функции main) прежде.
// чем завершиться, будет ждать окончания myThread
}
Создается поток обманчиво просто. Вам необходимо лишь создать объект thread
в стеке или в динамической памяти и передать ему функтор, который укажет, с чего начать работу. В данном случае термин «поток» (thread) используется в двух смыслах. Во-первых, это объект класса thread
, который является обычным объектом C++. При ссылке на этот объект я буду говорить «объект потока». Кроме того, существует поток выполнения, который является потоком операционной системы, представленным объектом thread
. Когда я говорю «поток» (в отличие от названия класса потока, напечатанного моноширинным шрифтом), я имею в виду поток операционной системы.
Теперь перейдем непосредственно к рассмотрению программного кода в примере. Конструктор thread
принимает функтор (или указатель функции), имеющий два аргумента и возвращающий void
. Рассмотрим следующую строку из примера 12.1.
boost::thread myThread(threadFun);
Она создает в стеке объект myThread
, являющийся новым потоком операционной системы, который начинает выполнять функцию threadFun
. В этот момент программный код функции threadFun
и код функции main
(по крайней мере, теоретически) выполняются параллельно. Конечно, на самом деле они могут выполняться не параллельно, поскольку ваша машина может иметь только один процессор, и в этом случае параллельная работа невозможна (благодаря недавно разработанным архитектурам процессоров это утверждение не совсем точное, но в настоящий момент я не буду принимать в расчет двухъядерные процессоры и т.п.). Если у вас только один процессор, то операционная система предоставит каждому созданному вами потоку квант времени в состоянии выполнения, перед тем как приостановить его работу. Так как эти кванты времени могут иметь различную величину, никогда нельзя с уверенностью сказать, какой из потоков раньше достигнет определенной точки. Именно в этой особенности многопоточного программирования заключается его сложность:
После создания потока myThread
поток main
продолжает свою работу, по крайней мере на мгновение, пока не достигнет следующей строки.
boost::thread::yield();
Это переводит текущий поток (в данном случае поток main) в неактивное состояние, что означает переключение операционной системы на другой поток или процесс, используя некоторую политику, которая зависит от операционной системы. С помощью функции yield
операционная система уведомляется о том, что текущий поток хочет уступить оставшуюся часть кванта времени. В это время новый поток выполняет threadFun
. После завершения threadFun
дочерний поток исчезает. Следует отметить, что объект thread
не уничтожается, потому что он является объектом С++, который по-прежнему находится в области видимости. Эта особенность играет важную роль.
Объект потока — это некий объект, существующий в динамической памяти или в стеке и работающий подобно любому другому объекту С++. Когда программный код выходит из области видимости потока, все находящиеся в стеке объекты потока уничтожаются, или, с другой стороны, когда вызывающая программа выполняет оператор delete
для thread*
, исчезает соответствующий объект thread
, который находится в динамической памяти. Но объекты thread
выступают просто как прокси относительно реальных потоков операционной системы, и когда они уничтожаются, потоки операционной системы не обязательно исчезают. Они просто отсоединяются, что означает невозможность их подключения в будущем. Это не так уж плохо.
Потоки используют ресурсы, и в любом (хорошо спроектированном) многопоточном приложении управление доступом к таким ресурсам (к объектам, сокетам, файлам, «сырой» памяти и т.д.) осуществляется при помощи мьютексов, которые являются объектами, обеспечивающими последовательный доступ к каким-либо объектам со стороны нескольких потоков (см. рецепт 12.2). Если поток операционной системы оказывается «убитым», он не будет освобождать свои блокировки и свои ресурсы, подобно тому как «убитый» процесс не оставляет шансов на очистку буферов или правильное освобождение ресурсов операционной системы. Простое завершение потока в тот момент, когда вам