1.如果用户提交任务都用一个类是不是比较麻烦?能不能用函数
作为用户来说,确实我想使用线程池,还要创建一个类,那样可太累了,所以我想直接放入函数就能运行,比如

这样使用是不是更加方便呢,但是具体怎么实现呢?
这里我们可以看到后面的两个参数是这个函数的两个形参可以想到模板参数
下面给出完整代码
cpp
template<typename Func, typename... Args>
auto submitTask(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>
{
if (!isPoolRunning_) {
std::cerr << "Thread pool is shutdown, cannot submit task." << std::endl;
using RType = decltype(func(args...));
auto dummyTask = std::make_shared<std::packaged_task<RType()>>(
[]() -> RType { return RType(); }
);
(*dummyTask)();
return dummyTask->get_future();
}
// 打包任务,放入任务队列里面
using RType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
);
std::future<RType> result = task->get_future();
// 获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
// 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回
if (!notFull_.wait_for(lock, std::chrono::seconds(1), [&] {
return taskQue_.size() < static_cast<size_t>(taskQueMaxThreshHold_);
})) {
// 表示notFull_等待1s种,条件依然没有满足
std::cerr << "task queue is full, submit task fail." << std::endl;
auto dummyTask = std::make_shared<std::packaged_task<RType()>>(
[]() -> RType { return RType(); }
);
(*dummyTask)();
return dummyTask->get_future();
}
// 如果有空余,把任务放入任务队列中
taskQue_.emplace([task]() { (*task)(); });
taskSize_++;
// 因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
// cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来
if (poolMode_ == PoolMode::MODE_CACHED
&& taskSize_ > idleThreadSize_
&& curThreadSize_ < threadSizeThreshHold_)
{
std::cout << ">>> create new thread..." << std::endl;
// 创建新的线程对象
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
int threadId = ptr->getId();
threads_.emplace(threadId, std::move(ptr));
// 启动线程
threads_[threadId]->start();
// 修改线程个数相关的变量
curThreadSize_++;
idleThreadSize_++;
}
// 返回任务的Result对象
return result;
}
这里的 auto submitTask(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>
typename Func
:这是一个模板类型参数,表示传入的任务函数的类型。Func
可以是任何可调用对象,如普通函数、函数指针、成员函数指针、lambda 表达式、std::function
对象等。typename... Args
:这是一个可变参数模板,...
表示可以接受任意数量和类型的参数。Args
是这些参数的类型集合。
所以这里和我们调用的形式刚刚好,这里用了引用折叠,参数会被完美转发给任务函数。
decltype(func(args...))
:decltype
是 C++ 的一个类型推导关键字,用于在编译时推导表达式的类型。func(args...)
表示调用任务函数func
并传入参数args...
,decltype(func(args...))
会推导出这个调用的返回类型。因此,std::future<decltype(func(args...))>
表示返回一个std::future
对象,该对象可以获取任务函数的返回结果。
这里采用了异步的方式,和我们之前的使用大同小异,这里显得更加方便,同时异步的方式可以获取抛异常,我们之前如果获取到其他的值也直接放回了
cpp
if (!isPoolRunning_) {
std::cerr << "Thread pool is shutdown, cannot submit task." << std::endl;
using RType = decltype(func(args...));
auto dummyTask = std::make_shared<std::packaged_task<RType()>>(
[]() -> RType { return RType(); }
);
(*dummyTask)();
return dummyTask->get_future();
}
对于这段代码来说,如果不能运行了,就返回一个空对象出去,这里是通过packaged_task这个和futuer搭配使用,它的get_futuer会返回一个futuer对象
cpp
// 打包任务,放入任务队列里面
using RType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
);
std::future<RType> result = task->get_future();
这里就是如果不是空,那么肯定是有参数的,那么这里就需要把参数绑定到函数中,所以哟用了bind函数
同时使用了完美转发保留了参数的左值还是右值属性
这里的 std::future<RType> result = task->get_future();j仅仅是获取一个对象,并没有执行任务
2.线程池的优雅停止
这里因为想到了如果我们在使用线程池,任务都完成了,我想把线程池通过一个信号把它结束,或者它在运行我一样也想结束它,那么就得优雅停止下来,不然整个程序就异常终止了,可能会有内存泄漏
怎么设计呢?,这里我使用了一个shutdown函数,用户自己调用,就可以实现线程池的优雅停止

这里是使用了shutdonwn()的,可以看到下面还有三个任务,值都是0
如果不使用的话

每个任务都会执行完毕
cpp
void shutdown(bool wait = true)
{
{
std::unique_lock<std::mutex> lock(taskQueMtx_);
if (!isPoolRunning_) return;
isPoolRunning_ = false;
// 等待所有任务完成
if (wait) {
while (taskSize_ > 0 || executingTaskCount_ > 0) {
taskFinished_.wait(lock);
}
}
// 清空队列
while (!taskQue_.empty())
{
taskQue_.pop();
taskSize_--;
}
notEmpty_.notify_all();
}
std::unique_lock<std::mutex> lock(taskQueMtx_);
exitCond_.wait(lock, [this] { return threads_.empty(); });
}
这是这个函数的主要设计
这里用了一个wait,如果直接调用,那么就会是true,这里加了一个任务完成的原子类型,executingTaskCount_ 如果任务或者没有执行的任务那么直接往下走,如果有,那么就等待,线程执行一个任务就会检查是不是为0,如果是0就通知taskFinished,拿到锁然后清空任务队列,通知所有线程,释放锁,这里各个线程看到线程池的isPoolRunning_ 是false就会知道关闭了,就会退出线程队列,这里会等待所有线程退出,然后结束shutdown函数,这样就实现了优雅停止
3.收尾
其实里面还有很多细节的改动,比如任务队列的包装什么的,但是这是两个比较大的改动,这里其实还有其他地方可以改动,比如很多地方可以用到c++20的语法,把该项目彻底改成c++20的项目,还有就是固定模式和动态变化的模式可以融合起来,这样省去了使用的麻烦,就加一个条件就行了,