Приведи пример задачи, решаемой с использованием многопоточности в C++.

«Приведи пример задачи, решаемой с использованием многопоточности в C++.» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Задача: Параллельное вычисление суммы элементов большого вектора.

Идея: Разделить вектор на несколько частей (по числу потоков), вычислить частичные суммы в каждом потоке параллельно, а затем сложить результаты в главном потоке.

Реализация на C++ с использованием <thread> и <future>:

#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <chrono>

// Функция для вычисления частичной суммы в диапазоне [begin, end)
int partial_sum(const std::vector<int>& data, size_t begin, size_t end) {
    return std::accumulate(data.begin() + begin, data.begin() + end, 0);
}

int main() {
    const size_t dataSize = 100'000'000;
    const int numThreads = std::thread::hardware_concurrency(); // Кол-во ядер CPU

    // 1. Генерируем большой вектор данных
    std::vector<int> bigData(dataSize, 1); // Вектор из единиц, сумма = dataSize

    // 2. Делим работу на диапазоны для потоков
    std::vector<std::future<int>> futures;
    size_t chunkSize = dataSize / numThreads;

    auto start = std::chrono::high_resolution_clock::now();

    // 3. Запускаем асинхронные задачи (потоки) через std::async
    for (int i = 0; i < numThreads; ++i) {
        size_t begin = i * chunkSize;
        size_t end = (i == numThreads - 1) ? dataSize : begin + chunkSize;
        // Запускаем вычисление частичной суммы в отдельном потоке
        futures.emplace_back(std::async(std::launch::async, partial_sum, std::ref(bigData), begin, end));
    }

    // 4. Собираем (редуцируем) результаты
    int totalSum = 0;
    for (auto& fut : futures) {
        totalSum += fut.get(); // get() блокирует, пока поток не вернёт результат
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    std::cout << "Total sum: " << totalSum << std::endl;
    std::cout << "Time taken with " << numThreads << " threads: " << duration.count() << " ms" << std::endl;

    // 5. Для сравнения — последовательная версия
    start = std::chrono::high_resolution_clock::now();
    int sequentialSum = std::accumulate(bigData.begin(), bigData.end(), 0);
    end = std::chrono::high_resolution_clock::now();
    duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "Sequential time: " << duration.count() << " ms" << std::endl;

    return 0;
}

Ключевые элементы C++ в примере:

  • std::thread::hardware_concurrency() — получение числа поддерживаемых аппаратно потоков.
  • std::async — высокоуровневый способ запуска асинхронной задачи, возвращающий std::future.
  • std::future — объект для получения результата из асинхронной операции.
  • std::ref() — передача ссылки на вектор в поток (по умолчанию аргументы копируются).

Почему это хороший пример: Задача легко распараллеливается (data parallelism), нет гонок данных (data races), так как каждый поток работает со своим диапазоном, и наглядно виден выигрыш в производительности на многоядерном процессоре.