Ответ
Задача: Параллельное вычисление суммы элементов большого вектора.
Идея: Разделить вектор на несколько частей (по числу потоков), вычислить частичные суммы в каждом потоке параллельно, а затем сложить результаты в главном потоке.
Реализация на 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), так как каждый поток работает со своим диапазоном, и наглядно виден выигрыш в производительности на многоядерном процессоре.
Ответ 18+ 🔞
А, ну вот, смотри, классическая задачка для демонстрации многопоточности. Типа, есть у тебя вектор, овердохуища размером, и надо всё это богатство просуммировать. Если в лоб, одним потоком — это, конечно, пиздец как долго, как будто на дворе 2002-й год и у тебя одно ядро.
Так, слушай сюда, как мы будем делать по-взрослому. Берём наш здоровенный вектор и режем его на куски, по количеству потоков, которые железо твоё может выдать. Каждый такой кусок — это как отдельная порция работы для своего потока. Потом мы эти потоки запускаем, они там своё дело делают, не мешая друг другу, потому что каждый в своём углу копается. А потом, когда все закончили, мы просто собираем их результаты в кучу — и вот она, общая сумма, готова. Всё просто, как три копейки, но в разы быстрее.
Смотри, вот код, я его чуть поясню, чтобы не было, как говорится, «подозрения ебать чувствую».
#include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <chrono>
// Функция, которая будет в каждом потоке считать сумму своего куска.
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(); // Спрашиваем у системы: "Скока потоков-то дашь?"
// 1. Набиваем вектор единичками. Сумма должна получиться ровно dataSize.
std::vector<int> bigData(dataSize, 1);
// 2. Готовим тару для будущих результатов.
std::vector<std::future<int>> futures;
size_t chunkSize = dataSize / numThreads; // Делим поровну на всех.
auto start = std::chrono::high_resolution_clock::now(); // Засекаем время.
// 3. Запускаем потоки! Каждому — свой кусок.
for (int i = 0; i < numThreads; ++i) {
size_t begin = i * chunkSize;
size_t end = (i == numThreads - 1) ? dataSize : begin + chunkSize; // Последний поток добирает хвост.
// Вот эта строчка — магия. std::async запускает задачу асинхронно.
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;
}
Что тут вообще происходит, по пунктам:
std::thread::hardware_concurrency()— это типа спросить у компа: «Э, дружище, скока у тебя виртуальных потоков-то?» Обычно это число ядер или в два раза больше.std::async— это просто красота. Ты говоришь: «Хочу запустить вот эту функцию вот с такими аргументами», а система тебе отвечает: «Окей, чувак, делаю», и кидает тебеstd::future— это такая расписка, что результат будет.std::future— это и есть та самая расписка. Когда тебе понадобится результат, ты вызываешьfut.get(). Если поток ещё не допилил свои вычисления — главный поток просто уснёт и будет ждать. Никаких гонок, всё цивильно.std::ref(bigData)— важный момент! По умолчанию аргументы в поток копируются. А копировать сто лямов элементов — это, я тебе скажу, терпения ноль ебать. Поэтому мы передаём ссылку, но оборачиваем её вstd::ref, чтобы компилятор не испугался.
Почему этот пример — огонь?
Потому что он, блядь, идеально распараллеливается. Каждый поток работает со своим куском памяти, они друг другу не мешают, не пишут в одно место — значит, никаких гонок данных (data races). Это как на заводе: у каждого рабочего свой станок, и они все вместе штампуют детали. А в конце начальник цеха (главный поток) просто всё собирает. На многоядерном процессоре ускорение будет просто ни хуя себе по сравнению с одним потоком. Вот и вся магия, никакой ебалы с мьютексами.