Потоки: зачем и как
Программа выполняет инструкции по одной: пока функция считает, остальная работа ждёт, даже если у процессора есть свободные ядра. Чтобы две задачи шли одновременно, нужен второй поток (thread).
Поток — последовательность инструкций, которая выполняется независимо от других. У каждой программы есть хотя бы один поток — тот, что выполняет main. Новый поток запускает объект std::thread из заголовка (header) <thread>:
#include <iostream>
#include <thread>
void print_squares() {
for (int i = 1; i <= 3; ++i) {
std::cout << i * i << ' '; // 1 4 9
}
}
int main() {
std::thread worker(print_squares);
worker.join();
std::cout << "done\n"; // done
}Запуск и ожидание
Конструктор std::thread создаёт поток и сразу запускает в нём print_squares. Функция main при этом не ждёт: следующая строка выполняется в главном потоке одновременно с новым. Вызов worker.join() останавливает main, пока поток worker не закончит работу. Поэтому done всегда печатается после квадратов.
Если бы main печатала done до join(), вывод двух потоков мог бы перемешаться. В каком порядке потоки выполняют свои инструкции, не определено.
Аргументы (arguments) для функции передают в конструктор после её имени: std::thread t(f, 1, 2) вызовет в новом потоке f(1, 2). Вместо функции можно передать и лямбду. Все потоки программы работают с общей памятью: глобальная переменная у них одна на всех.
detach и jthread
Вместо join() можно вызвать detach(): поток продолжит работать сам по себе, но дождаться его будет уже нельзя. Это рискованно. Если такой поток читает локальные переменные функции, которая его запустила, он может обратиться к ним после их уничтожения. Это неопределённое поведение (undefined behavior).
В C++20 появился std::jthread. Запускают его так же, как std::thread, но деструктор std::jthread сам вызывает join(). Поэтому забыть дождаться такого потока нельзя.
Частая ошибка
Поток запустили, но не дождались:
int main() {
std::thread worker(print_squares);
std::cout << "done\n";
} // ✗terminate called without an active exceptionПрограмма собирается без предупреждений. Но при выходе из main объект worker уничтожается, хотя для него не вызван ни join(), ни detach(). В таком случае деструктор std::thread вызывает std::terminate, и программа завершается аварийно. Вызовите join() до конца области видимости или используйте std::jthread.