Grupo de subprocesos 3 (Parte 3 - Diseño principal del grupo de subprocesos)

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 Tes 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 autoel 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

Supongo que te gusta

Origin blog.csdn.net/qq_35326529/article/details/130916737
Recomendado
Clasificación