Перейти к содержимому
Теория · Многопоточность: обзор · урок 1 из 10

Потоки: зачем и как

Программа выполняет инструкции по одной: пока функция считает, остальная работа ждёт, даже если у процессора есть свободные ядра. Чтобы две задачи шли одновременно, нужен второй поток (thread).

Поток — последовательность инструкций, которая выполняется независимо от других. У каждой программы есть хотя бы один поток — тот, что выполняет main. Новый поток запускает объект std::thread из заголовка (header) <thread>:

C++
#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(). Поэтому забыть дождаться такого потока нельзя.

Частая ошибка

Поток запустили, но не дождались:

C++
int main() {
    std::thread worker(print_squares);
    std::cout << "done\n";
}  // ✗
TEXT
terminate called without an active exception

Программа собирается без предупреждений. Но при выходе из main объект worker уничтожается, хотя для него не вызван ни join(), ни detach(). В таком случае деструктор std::thread вызывает std::terminate, и программа завершается аварийно. Вызовите join() до конца области видимости или используйте std::jthread.