Концепция — это абстрактное описание чего-то, обычно класса и его поведения, причем не делается никаких предположений относительно реализации. Как правило, сюда входит описание действий при конструировании и уничтожении, а также описание каждого метода с указанием предусловий, параметров и постусловий. Например, концепция мьютекса (Mutex) описывается как нечто допускающее блокирование и разблокирование только одним потоком в данный момент времени. Модель — это конкретная реализация концепции, например класс mutex в библиотеке Boost Threads. Уточнение (refinement) концепции — это некая ее специализация, например ReadWriteMutex, т.е. мьютекс с некоторыми дополнительными возможностями.

Наконец, потоки делают что-то одно из трех: работают, находятся в ожидании чего-то или готовы начать работу, но ничего не ожидают и не выполняют никаких действий. Эти состояния носят названия состояний выполнения (run), ожидания (wait) и готовности (ready). Эти термины я использую в последующих рецептах.

12.1. Создание потока

Проблема

Требуется создать поток (thread) для выполнения некоторой задачи, в то время как главный поток продолжает свою работу.

Решение

Создайте объект класса thread и передайте ему функтор, который выполняет данную работу. Создание объекта потока приведет к инстанцированию потока операционной системы, который начинает выполнять оператор operator() с вашим функтором (или начинает выполнять функцию, переданную с помощью указателя). Пример 12.1 показывает, как это делается.

Пример 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 (по крайней мере, теоретически) выполняются параллельно. Конечно, на самом деле они могут выполняться не параллельно, поскольку ваша машина может иметь только один процессор, и в этом случае параллельная работа невозможна (благодаря недавно разработанным архитектурам процессоров это утверждение не совсем точное, но в настоящий момент я не буду принимать в расчет двухъядерные процессоры и т.п.). Если у вас только один процессор, то операционная система предоставит каждому созданному вами потоку квант времени в состоянии выполнения, перед тем как приостановить его работу. Так как эти кванты времени могут иметь различную величину, никогда нельзя с уверенностью сказать, какой из потоков раньше достигнет определенной точки. Именно в этой особенности многопоточного программирования заключается его сложность: состояние многопоточной программы недетерминировано. При выполнении несколько раз одной и той же многопоточной программы можно получить различные результаты. Темой рецепта 12.2 является координация ресурсов, используемых несколькими потоками.

После создания потока myThread поток main продолжает свою работу, по крайней мере на мгновение, пока не достигнет следующей строки.

boost::thread::yield();

Это переводит текущий поток (в данном случае поток main) в неактивное состояние, что означает переключение операционной системы на другой поток или процесс, используя некоторую политику, которая зависит от операционной системы. С помощью функции yield операционная система уведомляется о том, что текущий поток хочет уступить оставшуюся часть кванта времени. В это время новый поток выполняет threadFun. После завершения threadFun дочерний поток исчезает. Следует отметить, что объект thread не уничтожается, потому что он является объектом С++, который по-прежнему находится в области видимости. Эта особенность играет важную роль.

Объект потока — это некий объект, существующий в динамической памяти или в стеке и работающий подобно любому другому объекту С++. Когда программный код выходит из области видимости потока, все находящиеся в стеке объекты потока уничтожаются, или, с другой стороны, когда вызывающая программа выполняет оператор delete для thread*, исчезает соответствующий объект thread, который находится в динамической памяти. Но объекты thread выступают просто как прокси относительно реальных потоков операционной системы, и когда они уничтожаются, потоки операционной системы не обязательно исчезают. Они просто отсоединяются, что означает невозможность их подключения в будущем. Это не так уж плохо.

Потоки используют ресурсы, и в любом (хорошо спроектированном) многопоточном приложении управление доступом к таким ресурсам (к объектам, сокетам, файлам, «сырой» памяти и т.д.) осуществляется при помощи мьютексов, которые являются объектами, обеспечивающими последовательный доступ к каким-либо объектам со стороны нескольких потоков (см. рецепт 12.2). Если поток операционной системы оказывается «убитым», он не будет освобождать свои блокировки и свои ресурсы, подобно тому как «убитый» процесс не оставляет шансов на очистку буферов или правильное освобождение ресурсов операционной системы. Простое завершение потока в тот момент, когда вам

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату