Многопоточность в C++: std::thread, std::async, std::mutex

Автор: | 15 июня, 2025

Современные процессоры имеют несколько ядер, что позволяет выполнять несколько задач одновременно. Чтобы эффективно использовать вычислительные ресурсы, C++ предоставляет инструменты многопоточности через стандартную библиотеку <thread>, <future> и <mutex>. В этой статье мы рассмотрим ключевые элементы многопоточности в C++: std::thread, std::async и std::mutex.


Что такое многопоточность?

Многопоточность — это способность программы выполнять несколько потоков выполнения одновременно. Каждый поток — это легковесный процесс, который работает независимо, но может разделять память с другими потоками.


std::thread — запуск потоков вручную

Класс std::thread позволяет запускать новый поток, передав ему функцию или лямбда-выражение:

Пример:

#include <iostream>
#include <thread>

void sayHello() {
    std::cout << "Привет из другого потока!" << std::endl;
}

int main() {
    std::thread t(sayHello); // Запуск потока
    t.join(); // Ожидание завершения потока
    std::cout << "Главный поток завершён." << std::endl;
    return 0;
}
  • t.join() — блокирует основной поток, пока t не завершится.
  • Вместо join() можно использовать detach() — поток продолжит выполнение независимо, но вы потеряете к нему доступ.

std::async — асинхронные задачи с future

std::async — удобный способ выполнить функцию в другом потоке и получить результат через std::future.

Пример:

#include <iostream>
#include <future>

int computeSquare(int x) {
    return x * x;
}

int main() {
    std::future<int> result = std::async(computeSquare, 5);
    std::cout << "Результат: " << result.get() << std::endl;
    return 0;
}
  • std::future::get() — блокирует поток до завершения задачи.
  • std::async может сам выбрать, запускать задачу в новом потоке или отложить выполнение — для принудительного запуска используйте флаг std::launch::async.
std::future<int> result = std::async(std::launch::async, computeSquare, 5);

std::mutex — защита общей памяти

Когда несколько потоков обращаются к одной и той же переменной, нужно синхронизировать доступ. Для этого используется std::mutex.

Пример:

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Итоговое значение: " << counter << std::endl;
    return 0;
}
  • std::lock_guard автоматически блокирует мьютекс при создании и разблокирует при выходе из области видимости.
  • Альтернатива: std::unique_lock — более гибкий, позволяет вручную блокировать/разблокировать мьютекс.

Краткие советы по многопоточности

  • Избегайте гонок данных — всегда защищайте общие ресурсы с помощью mutex.
  • Используйте std::async, если нужно просто выполнить задачу и получить результат — это безопаснее, чем ручное управление потоками.
  • Не забывайте про join() или detach() для всех std::thread — иначе поток завершится аварийно.
  • По возможности применяйте RAII-обёртки (lock_guard, unique_lock) — они предотвращают утечки и ошибки.

Заключение

Многопоточность в C++ — мощный инструмент, но требующий аккуратного обращения. С помощью std::thread, std::async и std::mutex вы можете эффективно распараллеливать задачи, управлять потоками и обеспечивать безопасный доступ к данным.