Написание типизированной обёртки над простым тред-пулом в C++ - это процесс создания абстракции, которая скрывает детали управления потоками, чтобы облегчить выполнение параллельных задач.
Прежде всего, требуется создать класс, представляющий типизированную обёртку над тред-пулом. Назовем его ThreadPool.
#include <functional> #include <thread> #include <vector> #include <queue> #include <mutex> #include <condition_variable> template<typename T> class ThreadPool { public: ThreadPool(size_t numThreads) : running(true) { for (size_t i = 0; i < numThreads; ++i) { workers.emplace_back([this]() { while (running) { std::unique_lock<std::mutex> lock(mtx); cond.wait(lock, [this]() { return !running || !tasks.empty(); }); if (!running && tasks.empty()) { return; } auto task = std::move(tasks.front()); tasks.pop(); lock.unlock(); task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(mtx); running = false; } cond.notify_all(); for (auto& worker : workers) { worker.join(); } } template<typename F, typename... Args> auto enqueue(F&& f, Args&&... args) -> std::future<T> { using return_type = typename std::result_of<F(Args...)>::type; auto task = std::make_shared<std::packaged_task<return_type()>>( std::bind(std::forward<F>(f), std::forward<Args>(args)...) ); std::future<T> res = task->get_future(); { std::unique_lock<std::mutex> lock(mtx); if (!running) { throw std::runtime_error("ThreadPool is stopped"); } tasks.emplace([task]() { (*task)(); }); } cond.notify_one(); return res; } private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; std::mutex mtx; std::condition_variable cond; bool running; };
Давайте разберём этот код по частям:
1. Включаем необходимые заголовочные файлы: functional, thread, vector, queue, mutex и condition_variable.
2. Создаем класс ThreadPool с использованием шаблона для типизации.
3. Cоздаем конструктор, принимающий количество потоков, которые будут доступны в пуле. Внутри конструктора мы создаем заданное количество потоков и помещаем их в массив workers. Каждый поток выполняет бесконечный цикл, в котором он ждет, пока есть задачи и пока флаг running установлен в true. Когда поток просыпается, он получает задачу из очереди tasks, выполняет ее и повторяет процесс до тех пор, пока running равен true.
4. Создаем деструктор для ThreadPool. Внутри деструктора мы сначала устанавливаем флаг running в false, чтобы остановить потоки. Затем мы оповещаем все потоки остановиться, используя условную переменную cond, и дожидаемся, пока все потоки завершатся с помощью вызова join() для каждого потока в массиве workers.
5. Создаем метод enqueue для добавления задачи в очередь. Метод enqueue принимает произвольную функцию f и ее аргументы args. Он создает упакованную задачу, используя std::bind и std::forward для передачи аргументов. Затем мы помещаем упакованную задачу в очередь tasks, где ее выполнение будет ожидать поток из пула. Метод возвращает std::future, который можно использовать для получения результата выполнения задачи.
Обратите внимание, что мы используем std::result_of для определения возвращаемого типа задачи, и используем std::forward для передачи аргументов в упакованную задачу.
Это основа типизированной обёртки над простым тред-пулом в C++. Вы можете использовать этот класс ThreadPool для создания параллельных задач и эффективного управления потоками. Он предоставляет гибкий и типобезопасный механизм для выполнения параллельных задач в C++.