Что такое поток? Поток — это рабочая единица определенного процесса. В мультипрограммных операционных системах одновременно выполняются разные процессы.
Аналогичным образом мы можем захотеть одновременно выполнять одни и те же экземпляры процессов. В этом случае каждый экземпляр процесса назначается исполнительной единице, называемой потоком. В многопоточной системе множество потоков выполняются одновременно независимо друг от друга.
До сегодняшней C++ была поддержка потоков POSIX. Но у этой функции были серьезные проблемы с переносимостью, поскольку она работала только в операционных системах Linux или UNIX. Таким образом, начиная с C++ 11-ой версии, появился единственный класс std::thread, который определяет всю функциональность для потоков. Классы и функции определены в заголовочном файле.
Работа с <thread>
Используя std::thread, нам просто нужно создать новый объект потока и передать ему вызываемый объект. Вызываемый объект — это исполняемый код, который мы хотим выполнить во время работы потока. Поэтому всякий раз, когда нам нужен новый поток, мы просто создаем объект std::thread и передаем вызываемый объект в качестве аргумента его конструктору.
После создания объекта std::thread запускается новый поток и выполняется код, предоставленный вызываемым объектом.
Давайте посмотрим, как мы можем определить вызываемый объект, который будет предоставлен объекту потока.
Вызываемый объект может быть определен тремя способами:
1) Использование функционального объекта
Мы можем использовать объект функции в качестве вызываемого в объекте потока. Для использования объекта функции нам нужен класс, и в этом классе мы перегружаем оператор (). Эта перегруженная функция содержит код, который должен выполняться при создании потока.
1 2 3 4 5 6 7 8 9 |
/</em>/ Define the class for function object class functioObject_class { // Оператор перегрузки () void operator()(params) { // исполняемый код } }; // Создание объекта потока |
std::thread thread_object(functioObject_class (), params) |
Обратите внимание на способ определения объекта потока. В качестве первого параметра конструктору объекта потока мы предоставляем перегруженную функцию, а затем указываем ее аргументы (params) в качестве второго аргумента.
2) Использование указателя функции
Вызываемый объект с помощью указателя функции может быть определен следующим образом:
1 2 3 4 |
void funct_call(params) //исполняемый код } |
Как только мы определим эту функцию, мы можем создать объект потока с этой функцией как вызываемой следующим образом:
std::thread thread_obj(funct_call, params); |
Обратите внимание, что аргументы (params), передаваемые функции, указываются после имени функции в объекте потока.
3) Использование лямбда-выражения
Мы также можем получить вызываемый объект как лямбда-выражение и передавать его объекту потока для выполнения. Фрагмент кода показан ниже:
1 2 3 4 |
// Определяем лямбда-выражение auto f = [](params) { // код для выполнения }; |
std::thread thread_object(f, params); |
В приведенном выше коде мы определили лямбда-выражение f, а затем передали его конструктору объекта потока в качестве первого аргумента, за которым следуют его параметры (params) в качестве второго аргумента.
std::thread join method |
В некоторых случаях мы можем захотеть, чтобы текущий исполняемый поток завершился до того, как мы начнем другое действие.
Классический пример — когда мы открываем приложение с графическим интерфейсом. В тот момент, когда мы открываем приложение, запускается поток для загрузки и инициализации графического интерфейса, и мы не можем выполнять какие-либо действия, если только загрузка и инициализация не выполняются правильно, чтобы обеспечить правильную работу графического интерфейса.
Класс std::thread предоставляет метод join(), который гарантирует, что текущий поток (pointed by *this) завершится первым, прежде чем будут предприняты какие-либо другие действия.
Вот пример:
1 2 3 4 5 6 7 |
int main() { std::thread t1(callable_code); ….. t1.join(); ….. } |
В приведенном выше примере функции main придется ждать, пока не завершится поток t1. Как правило, функция соединения потока блокирует другие действия/функции до тех пор, пока вызов потока не завершит свое выполнение.
Пример потока
Мы представляем полный пример кода для создания и выполнения потока в программе, показанной ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <iostream> #include <thread> using namespace std; // функция, которая будет использоваться в вызываемом объекте void func_dummy(int N) { for (int i = 0; i < N; i++) { cout << "Thread 1 :: callable => function pointer\n"; } } // Вызываемый объект class thread_obj { public: void operator()(int n) { for (int i = 0; i < n; i++) cout << "Thread 2 :: callable => function object\n"; } }; int main() { // Определение лямбда-выражения auto f = [](int n) { for (int i = 0; i < n; i++) cout << "Thread 3 :: callable => lambda expression\n"; }; //запустить поток, используя указатель функции в качестве вызываемого объекта thread th1(func_dummy, 2); // запустить поток, используя объект функции в качестве вызываемого объекта thread th2(thread_obj(), 2); //запустить поток, используя лямбда-выражение в качестве вызываемого объекта thread th3(f, 2); // Дождитесь завершения потока t1 th1.join(); // Дождитесь завершения потока t2 th2.join(); // Дождитесь завершения потока t3 th3.join(); return 0; } |
Вывод данных:
1 2 3 4 5 6 |
Thread 1 :: callable => function pointer Thread 1 :: callable => function pointer Thread 3 :: callable => lambda expression Thread 3 :: callable => lambda expression Thread 2 :: callable => function object Thread 2 :: callable => function object |
В приведенном выше примере мы создали три потока, используя три разных вызываемых объекта, то есть указатель функции, объект и лямбда-выражение. Мы создаем 2 экземпляра каждого потока и запускаем их. Как показано в выводных данных, три потока работают одновременно независимо друг от друга.
Итог
В этой статье мы рассмотрели концепции многопоточности в C++ на наглядном примере. В нашей следующей статье мы изучим различные аспекты шаблонов в C++.
С Уважением, МониторБанк