**C/C++ 线程池的简单封装
最近在搭建一个服务器,打算把线程池应用进去,根据老师课上所讲和网上前辈们提供的资料对线程池有了以下总结:**
一、线程的创建需要内存资源,线程的创建和销毁需要时间资源
二、线程池作用:
如果项目需要频繁的创建线程来处理任务,那么CPU资源会在线程创建和销毁上浪费许多,所以我们可以先把一定量的线程先创建好并且让它们在没任务的情况下陷入睡眠,有任务时再被唤醒来处理任务;
三、线程池组成部分:
(1) 线程池管理器: 对线程进行创建与管理
(2) 工作线程: 线程池中的线程
(3) 任务接口(任务类内):供线程调度进行任务的执行
(4) 任务队列: 存放没用处理的任务,提供一种缓冲机制
四、 封装源码
1、类声明
#pragma once
#include <queue>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include "CTask.h"
class CThreadPool
{
public:
CThreadPool(int num);
~CThreadPool();
public: // 线程内调用
void lock_mutex(); // 上锁
void unlock_mutex(); // 解锁
void wait_for_wake(); // 线程进入睡眠,等待被唤醒
void pick_task(); // 从任务队列内拿任务并执行
int get_pid();
public:
void work(); // 生成工作线程
void add_task(CTask* &task); // 添加任务到任务队列, 并且唤醒一个线程取任务
private:
int initTotalNum; // 初始化线程总数
bool bStop; // 结束标志位
std::queue<CTask*>task_que; //任务队列
pthread_cond_t cond; //线程唤醒的条件变量
pthread_mutex_t mutex; //互斥锁
pthread_t pid; //线程id
};
2、 类定义
#include "CThreadPool.h"
#include <iostream>
using namespace std;
//void* start_routine_A(void* arg);
// 核心线程回调函数
void* start_routine_A(void* arg)
{
CThreadPool* pool = (CThreadPool*)arg; //获得本类对象
while (1)
{
//1、 用条件变量阻塞线程(也就是让线程睡眠等待唤醒)
pool->lock_mutex(); //锁上cond, 保证只能cond被用的情况下不会抢占
cout << pthread_self() << " is waitting for task..." << endl;
//pool->wait_for_wake(pool); //pthread_cond_wait()函数被调用,锁会自动解开,返回的时候在被锁上
pool->wait_for_wake();
cout << pthread_self() << " is wake up for task......" << endl;
pool->unlock_mutex(); //公共条件遍历用完就解开锁,尽可能的缩小锁范围
//2、 线程唤醒后从任务队列内拿任务执行
pool->pick_task();
cout << "core end." << endl;
}
}
CThreadPool::CThreadPool(int num):initTotalNum(num)
{
//初始化两个锁
pthread_cond_init(&this->cond, NULL);
pthread_mutex_init(&this->mutex, NULL);
}
void CThreadPool::lock_mutex()
{
pthread_mutex_lock(&this->mutex);
}
void CThreadPool::unlock_mutex()
{
pthread_mutex_unlock(&this->mutex);
}
void CThreadPool::wait_for_wake()
{
pthread_cond_wait(&this->cond, &this->mutex);
cout << "core Thread is wake up, pid = " << this->pid << endl;
}
void CThreadPool::pick_task()
{
this->lock_mutex(); //队列也是公共资源,用之前先上锁
if (!this->task_que.empty())
{
CTask* t = this->task_que.front(); //从队列拿任务(注意:队列是先进先出,栈是先进后出)
this->task_que.pop(); //拿了就踢出队列
this->unlock_mutex(); //公共资源用完解锁
t->run();
}
else
{
cout << "wocao?" << endl;
this->unlock_mutex(); //队列为空就解锁,避免进入死锁
}
}
void CThreadPool::work()
{
// 先创建核心线程
for (int i = 0; i < this->core_num; i++)
{
pthread_create(&this->pid, NULL, start_routine_A, this);
cout << "The " << i + 1 << " core thread has been created." << endl;
sleep(1);
}
// 初始化当前线程数
this->thread_num = core_num;
}
void CThreadPool::add_task(CTask* &task)
{
this->task_que.push(task); //加入任务队列
// 当前任务队列长度 > 核心线程数 && 不超过最大线程数?创建非核心线程 : 保持;
if (task_que.size() > core_num && thread_num <= initTotalNum)
{
this->increa_thread_num();
pthread_create(&this->pid, NULL, start_routine_B, this);
// sleep(1);
pthread_cond_signal(&this->cond);
return;
}
pthread_cond_signal(&this->cond); //唤醒线程
}
五、关键点
1、pthread_cond_t cond 是线程的公共资源,线程在使用条件变量时需要有上锁和解锁的操作;
2、pthread_cond_wait(); 会在调用的时候自动给mutex解锁,等到返回时会还原锁状态;
六、后续优化方向
1、 设置核心线程数+ 最大线程数 : 开始仅初始化核心线程数的工作线程,在任务量大于核心线程数的情况下再挨个创建非核心线程进行任务处理;
2、 非核心线程超时等待自动销毁: 会应用到pthread_cond_timedwait() ; 其中的超时参数为绝对时间,如果传递错误会出现阻塞失效的情况(也就是仔等待时间内却直接超时返回)
3、 结束标志位的使用: 可以看到我这里虽然声明了结束标志位却没有用到,因为我暂时也不太清楚该用在哪里最合适,所以暂且放着等待下一个优化版本再加上;