C++线程池任务实现(简洁版)-创新互联

    • 一、前言
    • 二、线程池中的任务实现
    • 三、测试
    • 四、总结

成都创新互联公司专注于门头沟企业网站建设,自适应网站建设,商城网站定制开发。门头沟网站建设公司,为门头沟等地区提供建站服务。全流程按需网站建设,专业设计,全程项目跟踪,成都创新互联公司专业和态度为您提供的服务一、前言

 线程池的目标实现线程的复用, 因为线程是操作系统级别的资源, 频繁的创建线程和销毁线程会影响程序的性能。它逻辑的是预先创建一定数量线程, 然后线程池中的线程分别向任务队列取任务来执行, 若当前没有可执行的任务, 则线程池中的线程进入睡眠状态, 避免空耗CPU资源。本文主要记录线程池模型中关于任务的实现。
 关于线程池中的任务实现, 目前我主要见过两种版本:
  ①第一种是利用C++多态性质来实现, 即创建一个 ITask 的虚基类, 然后各个模块根据应用场景去覆写虚基类,通常是基类中声明一个纯虚函数,派生类必须覆写这个函数接口。比如

class ITask
{public:
	virtual bool Run() = 0;
	
	//other interface
	...
}

class myTask:public ITask
{public:
	virtual bool Run() override
	{ //具体任务的流程
	}
	...
}

任务队列中保存的是基类 ITask 的指针, 线程池中的任务取出任务后调用实际调用派生类的 函数 Run() 执行任务, 通过这种多态封装各种类型的任务。 POCO库中的线程池就是用的这种方式
  ①第二种是利用仿函数,结合C++11中的 Lambda表达式和函数绑定器来实现, github上搜索C++版本的 《theradpool》出来的第一个项目就是用的这种方式, 本文展现的就是这种方式的更多细节。

二、线程池中的任务实现

  C++中可被调用对象分别为以下三种:

① 函数,接受额外传入的参数里列表 作为实参(argument)
② 指向成员函数的指针,当你通过对象调用它,该对象被传递成为第一实参(必须是个reference或pointer),其他实参则一一对应成员函数的参数。
③ 函数对象(function object,该对象拥有operator ()),附带的args被传递作为实参。比如 Lambda 表达式。

前两种方式因为函数的参数有太多不确定性了, 所以通常不能直接用来做线程任务,但是函数对象就方便了, 它有统一的调用方式,并且可以把函数或者指向成员的函数指针包装成 函数对象。所以任务的数据数据类型声明为 std::function
并且它的相关构造、赋值、移动、拷贝函数如下:

class CXTask
{public:
   using Function = std::function;

public:
   inline CXTask();
   inline CXTask(const CXTask& another);
   inline CXTask(CXTask&& another) noexcept;
   inline CXTask& operator=(const CXTask& another);
   inline CXTask& operator=(CXTask&& another);

   inline CXTask(const Function& func);
   inline CXTask(Function&& func);
   inline CXTask& operator=(Function&& func);
   inline CXTask& operator=(const Function& func);

   inline void operator() () const;
   inline operator bool() const;

private:
   Function m_func;
};

 声明为这种方式的任务后线程池中的线程在队列中获得一个任务后 可以直接通过 小括号 task() 执行任务。接下来需要把前文提到的前两种可调用对象转换为 CXTask 类型。具体实现如下:

//任意类型函数和参数
templateinline CXTask CreateTask(Function&& func, Args&&... args)
{return CXTask(std::bind(std::forward(func), std::forward(args)...));
}

//函数对象, 比如 lambda
templateinline CXTask CreateTask(Function&& func)
{return CXTask(std::forward(func));
}

有了以上的实现后就可以把各种 DIY 的函数放进 CXTask 的任务队列了, 详见测试代码。

三、测试

  测试程序使用C++标准库的 std::queue 来缓存线程任务, 创建两个线程来模拟线程池来执行任务, 使用之前文章中介绍的 (CXEvent) 来进行线程同步。功能上输入数字通过lambda创建任务, 输入字母通过函数绑定器创建对象,按 ‘q’ 退出程序。详细代码如下:

queueg_tasklist;
CXEvent       g_task_notify;
CXEvent       g_quit_notify(CXEvent::Mode::Manual);

void ThreadFunc()
{while (!g_quit_notify.isSignal())
	{g_task_notify.Wait();
		if (g_tasklist.empty())
		{	continue;
		}

		auto task = g_tasklist.front();
		if (task)
		{	task();
		}
		g_tasklist.pop();
	}
}

void TaskFunc(char inputcmd)
{cout<< "func task -- cur threadID:"<< this_thread::get_id()<< " --- keyWord is: "<< inputcmd<< endl;
}

int main()
{thread th1(ThreadFunc);
	thread th2(ThreadFunc);
	cout<<"th1 id: "<'R' };
	cin.get(inputCmd);
	while ('q' != inputCmd)
	{//数字走 lambda创建 task
		if (inputCmd>='0'&& inputCmd<='9')
		{	g_tasklist.push(CreateTask([inputCmd] {		cout<< "lambda task -- cur threadID:"<< this_thread::get_id()<< " --- keyWord is: "<< inputCmd<< endl;
				}));
		}
		
		//字母走 函数创建 task
		if (inputCmd >= 'a' && inputCmd<= 'z')
		{	g_tasklist.push(CreateTask(TaskFunc, inputCmd));
		}

		g_task_notify.SetEvent();
		
		cin.clear();
		cin.ignore();
		cin.get(inputCmd);
	}
	
	g_quit_notify.SetEvent();

	if (th1.joinable())
	{th1.join();
	}

	if (th2.joinable())
	{th2.join();
	}
	   
	return 0;
}

测试结果如下:
在这里插入图片描述

四、总结

 本文实现了一种简洁版的线程池任务,实际工作中当然要比这个要复杂很多,比如通过观察者模式引入通知系统, 线程池的调度,任务的优先级管理等等。不过有了这些基础后就能方便看懂一些开源线程池的实现,提高技术。哈哈哈
代码地址:https://github.com/pengguoqing/samples_code/tree/master/c%2B%2B/task

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章名称:C++线程池任务实现(简洁版)-创新互联
本文路径:http://pwwzsj.com/article/dhihgh.html