Выбрать главу

Разумеется, перед тем задумываться о применении платформенно-зависимых средств, стоит как следует разобраться в том, что предлагает стандартная библиотека, поэтому начнем с примера.

1.4. В начале пути

Итак, вы получили новенький, с пылу с жару компилятор, совместимый со стандартом С++11. Что дальше? Как выглядит многопоточная программа на С++? Да примерно так же, как любая другая программа, — с переменными, классами и функциями. Единственное существенное отличие состоит в том, что некоторые функции могут работать параллельно, поэтому нужно следить за тем, чтобы доступ к разделяемым данным был безопасен (см. главу 3). Понятно, что для параллельного исполнения необходимо использовать специальные функции и объекты, предназначенные для управления потоками.

1.4.1. Здравствуй, параллельный мир

Начнем с классического примера — программы, которая печатает фразу «Здравствуй, мир». Ниже приведена тривиальная однопоточная программа такого сорта, от нее мы будем отталкиваться при переходе к нескольким потокам.

#include <iostream>

int main() {

 std::cout << "Здравствуй, мир\n";

}

Эта программа всего лишь выводит строку Здравствуй мир в стандартный поток вывода. Сравним ее с простой программой «Здравствуй, параллельный мир», показанной в листинге 1.1, — в ней для вывода сообщения запускается отдельный поток.

#include <iostream>

#include <thread> ← (1)

void hello()      ← (2)

{

 std::cout << "Здравствуй, параллельный мир\n";

}

int

main() {

 std::thread t(hello); ← (3)

 t.join();             ← (4)

}

Прежде всего, отметим наличие дополнительной директивы #include <thread> (1). Все объявления, необходимые для поддержки многопоточности, помещены в новые заголовочные файлы; функции и классы для управления потоками объявлены в файле <thread>, а те, что нужны для защиты разделяемых данных, — в других заголовках.

Далее, код вывода сообщения перемещен в отдельную функцию (2). Это объясняется тем, что в каждом потоке должна быть начальная функция, в которой начинается исполнение потока. Для первого потока в приложении таковой является main(), а для всех остальных задается в конструкторе объекта std::thread. В данном случае в качестве начальной функции объекта типа std::thread, названного t (3), выступает функция hello().

Есть и еще одно отличие вместо того, чтобы сразу писать на стандартный вывод или вызывать hello() из main(), эта программа запускает новый поток, так что теперь общее число потоков равно двум: главный, с начальной функцией main(), и дополнительный, начинающий работу в функции hello().

После запуска нового потока (3) начальный поток продолжает работать. Если бы он не ждал завершения нового потока, то просто дошел бы до конца main(), после чего исполнение программы закончилась бы быть может, еще до того, как у нового потока появился шанс начать работу. Чтобы предотвратить такое развитие событие, мы добавили обращение к функции join() (4); в главе 2 объясняется, что это заставляет вызывающий поток (main()) ждать завершения потока, ассоциированного с объектом std::thread, — в данном случае t.

Если вам показалось, что для элементарного вывода сообщения на стандартный вывод работы слишком много, то так оно и есть, — в разделе 1.2.3 выше мы говорили, что обычно для решения такой простой задачи не имеет смысла создавать несколько потоков, особенно если главному потоку в это время нечего делать. Но далее мы встретимся с примерами, когда запуск нескольких потоков дает очевидный выигрыш.

1.5. Резюме

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