扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
线程池的目标实现线程的复用, 因为线程是操作系统级别的资源, 频繁的创建线程和销毁线程会影响程序的性能。它逻辑的是预先创建一定数量线程, 然后线程池中的线程分别向任务队列取任务来执行, 若当前没有可执行的任务, 则线程池中的线程进入睡眠状态, 避免空耗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元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流