Esta parte es principalmente la implementación del grupo de subprocesos y luego escribir una interfaz externa, enviar la tarea a la cola de tareas y luego usar subprocesos múltiples para procesar la tarea. (El contenido principal de esta parte es el análisis del tipo de retorno de la función de tarea y el análisis de la función con múltiples parámetros de entrada. Es necesario asegurarse de que el grupo de subprocesos pueda analizar y ejecutar múltiples parámetros de entrada).
Las funciones aquí usan principalmente la clase <funcional>, usando std::bind() para enlazar funciones y parámetros. Los parámetros usan una nueva función de c++11, plantillas de parámetros variables. Se puede lograr.
class ThreadPool
{
public:
//线程池构造函数
ThreadPool();
//1, 提交函数
template<typename F, typename... Args>
auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))> ;
// 析构函数
inline ~ThreadPool();
private:
// 线程池
std::vector<std::thread> worker_;
// 任务队列
BoundedQueue<std::function<void()>> task_queue_;
// 退出线程池标志位
std::atomic_bool stop_;
};
1. Constructor
//线程池构造函数
explicit ThreadPool(std::size_t thread_mun, std::size_t max_task_num = 1000) : stop_(false)
{
// 初始化失败抛出异常
if (!task_queue_.Init(max_task_num, new SleepWaitStratrgy)) { // SleepWaitStratrgy BlockWaitStrategy
throw std::runtime_error("Task queue init failed");
}
// 存放多个 std::thread线程对象
worker_.reserve(thread_mun);
for (size_t i = 0; i < thread_mun; i++)
{
// 使用lamdba 表达式来创建每个线程
// 功能是 从任务队列中获取任务,并且执行任务的函数对象
worker_.emplace_back([this] {
while (!stop_)
{
std::function<void()> task;
if (task_queue_.WaitDequeue(&task)) {
task();
}
}
});
}
}
1. Inicialice la cola de tareas.
2. Modifique el tamaño de un objeto de hilo.
3. En la cola de subprocesos, construya subprocesos y utilice la función WaitDequeue(&task) a su vez para obtener tareas del grupo de subprocesos. y ejecutar la tarea.
2. Enviar función de tarea
template<typename F, typename... Args>
auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))> {
std::function<decltype(f(args...))()> func =
std::bind(std::forward<F>(f), std::forward<Args>(args)...);
auto task =
std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
// 任务队列的入队 并且唤醒线程
task_queue_.Enqueue([task]() {(*task)();});
return task->get_future();
}
Esta es también la parte clave central.
1. Se usa la plantilla de función principal y la plantilla de parámetros variables acepta múltiples parámetros.
template<typename F, typename... Args>
2. El nombre de la función de encabezado
auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))>
auto: deduce automáticamente el tipo de función.
Enqueue(F&& f, Args &&...args): dos parámetros de entrada, uno es una referencia universal y el otro es una plantilla de parámetro variable. (El && aquí no es una referencia de valor r. La referencia universal puede entenderse simplemente como, cuando T
es un parámetro de plantilla, T&&
su función principal es mantener la categoría de valor para el reenvío).
->std::future<decltype(f(args...))> : La palabra clave decltype en c++11 resuelve auto
el defecto de que la palabra clave solo puede deducir el tipo de variable. Utilizado para compensar auto, el tipo de función no se puede deducir directamente.
std::future<>: Derivación asíncrona, llame al resultado cuando sea necesario, sin bloqueo de funciones y luego en su lugar, esperando que decltype deduzca el tipo de función.
3,
std::function<decltype(f(args...))()> func =
std::bind(std::forward<F>(f), std::forward<Args>(args)...);
Defina un tipo de función como una clase de plantilla de función general, use decltype para deducir el tipo de función. Luego use la función de vinculación en std::function para vincular la función y sus parámetros. El reenvío perfecto de std::forward() se usa aquí porque la "f" aquí no es un valor r cuando se pasa, es una "referencia". Sin embargo, un objeto vinculado a una referencia perfecta puede tener un valor a la izquierda . también puede ser un rvalue, y es debido a esta ambigüedad que surge std::forward
.
4,
auto task = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
Aquí usamos el método std::make_shared, declaramos un puntero inteligente de tipo std::packaged_task<decltype(f(args...))()> y pasamos la función anterior como un parámetro de std::package_task . Los punteros inteligentes son más convenientes para administrar package_task.
package_tas se puede usar para encapsular cualquier objetivo al que se pueda llamar, para implementar llamadas asincrónicas.
5,
task_queue_.Enqueue([task]() {(*task)();});
return task->get_future();
La tarea se pone en cola y luego se llama al método get_future() de la tarea para obtener el resultado asíncrono.
Devolvemos un objeto futuro, para que podamos devolvérselo a la persona que llama, y la persona que llama decide cuándo llamar al método get() para obtener el resultado de la operación asincrónica.
3. Destructor
// 析构函数
inline ~ThreadPool() {
// 防止重复析构, (将stop_改变成true,并且返回之前的值)
if (stop_.exchange(true)) {
return;
}
// 线程池中所有线程停止
task_queue_.BreakAllWait();
// 并且等待他们完成所有任务
for (std::thread& worker : worker_)
{
worker.join();
}
}
El destructor aquí es básicamente la función básica de la lógica del destructor, que libera todos los recursos.
Hasta ahora, todo el grupo de subprocesos ha terminado.
Referencia: Implementación de grupo de subprocesos basado en C++ 11 - Saber casi