连接
https://gitee.com/lzhiqiang1999/thread-pool
欢迎star
一 线程池
1 含义
线程池就是把一堆线程进行统一管理,避免线程过多带来的资源浪费
2 为什么要使用线程池
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性
3 什么地方可以使用线程池
(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
(2)IO密集型的任务。因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务。
(3)计算密集型任务。线程池中的线程数设置得少一些,减少线程上下文的切换。
(4)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦
二 C++实现一个简单的线程池
1 流程示意图
2 组件
(1)任务队列类
(2)线程池类
3 任务队列类
- 管理任务
任务的组成:
- 函数指针, 指向一个功能函数, 这函数中的操作最终被子线程处理
- arg: 给回调函数传参
#include <queue>
#include <pthread.h>
#include <string>
#include <iostream>
//
任务结构体
struct Task
{
Task()
{
function = nullptr;
arg = nullptr;
}
void *(*function)(void *); /* 函数指针,回调函数 */
void *arg; /* 上面函数的参数 */
};
typedef void* (*callback)(void*);
// 任务队列类
class TaskQueue
{
public:
TaskQueue();
~TaskQueue();
// 往任务队列中添加任务
void addTask(Task &task);
void addTask(callback func, void* arg);
// 获取当前队列中的任务数量
inline size_t taskNumber()
{
return m_queue.size();
}
// 从队列中取出一个任务
Task takeTask();
private:
pthread_mutex_t m_mutex; // 互斥锁,多个线程可能会同时操作任务队列
std::queue<Task> m_queue;
};
TaskQueue::TaskQueue()
{
pthread_mutex_init(&m_mutex, NULL);
}
TaskQueue::~TaskQueue()
{
pthread_mutex_destroy(&m_mutex);
}
void TaskQueue::addTask(Task & task)
{
pthread_mutex_lock(&m_mutex);
m_queue.push(task);
pthread_mutex_unlock(&m_mutex);
}
void TaskQueue::addTask(callback func, void * arg)
{
pthread_mutex_lock(&m_mutex);
Task t;
t.arg = arg;
t.function = func;
m_queue.push(t);
pthread_mutex_unlock(&m_mutex);
}
Task TaskQueue::takeTask()
{
if (m_queue.size() > 0)
{
pthread_mutex_lock(&m_mutex);
Task t = m_queue.front();
m_queue.pop();
pthread_mutex_unlock(&m_mutex);
return t;
}
return Task();
}
4 线程池类
- 管理了多个线程
- 管理者线程 :1个
- 控制工作线程的个数
- 发现快不够用了: 创建一批新的线程
- 不工作的线程太多了: 销毁一批
- 工作的线程 :n个
- 任务来自于任务队列
- 工作的线程处理逻辑:
- 不停的读任务队列
- 为空: 工作线程阻塞 -> 使用条件变量
- 不为空: 从任务队列中取出一个任务
- struct Task -> 任务的类型
- 调用Task中对应的函数, 进行逻辑处理
- 不停的读任务队列
- 管理者线程 :1个
- 当线程阻塞了, 什么时候唤醒?
- 往任务队列中添加任务的时候唤醒阻塞线程即可
#include <pthread.h>
#include "ThreadPool.h"
#include "TaskQueue.h"
#include <iostream>
const int DEFAULT_TIME = 10; /*10s检测一次*/
const int MIN_WAIT_TASK_NUM = 10; /*如果queue_size > MIN_WAIT_TASK_NUM 添加新的线程到线程池*/
const int DEFAULT_THREAD_VARY = 10; /*每次创建和销毁线程的个数*/
// 线程池类
class ThreadPool
{
public:
// min: 最少线程个数, max: 最大线程个数
ThreadPool(int min, int max);
~ThreadPool();
// 给线程池添加任务
void addPoolTask(Task &task);
private:
// 判断线程是否还存活着
bool threadIsAlive(pthread_t tid);
// 工作线程回调函数
void* threadWorking(void* arg);
// 管理者线程回调函数
void* threadManager(void* arg);
private:
pthread_mutex_t lock; /* 用于锁住当前类成员 */
pthread_mutex_t thread_counter; /* 记录忙状态线程个数de琐 -- busy_thr_num */
pthread_cond_t queue_not_empty; /* 任务队列里不为空时,通知等待任务的线程 */
pthread_t *threads; /* 存放线程池中每个线程的tid。数组 */
pthread_t adjust_tid; /* 存管理线程tid */
TaskQueue *task_queue; /* 任务队列 */
int min_thr_num; /* 线程池最小线程数 */
int max_thr_num; /* 线程池最大线程数 */
int live_thr_num; /* 当前存活线程个数 */
int busy_thr_num; /* 忙状态线程个数 */
int wait_exit_thr_num; /* 要销毁的线程个数 */
};
(1)初始化
- 最多线程,最少线程
- 创建最少线程
- 创建管理者线程
ThreadPool::ThreadPool(int min, int max)
{
// 实例化任务队列
task_queue = new TaskQueue;
do
{
min_thr_num = min;
max_thr_num = max;
busy_thr_num = 0;
live_thr_num = min_thr_num; /* 活着的线程数 初值=最小线程数 */
/* 根据最大线程上限数, 给工作线程数组开辟空间, 并清零 */
threads = new pthread_t[sizeof(pthread_t) * max_thr_num];
if (threads == NULL)
{
break;
}
memset(threads, 0, sizeof(pthread_t) * max_thr_num);
/* 初始化互斥琐、条件变量 */
if (pthread_mutex_init(&(lock), NULL) != 0
|| pthread_mutex_init(&(thread_counter), NULL) != 0
|| pthread_cond_init(&(queue_not_empty), NULL) != 0)
{
break;
}
/* 启动 min_thr_num 个 work thread */
for (int i = 0; i < min_thr_num; i++)
{
// 创建并启动工作的线程
pthread_create(&threads[i], NULL, threadWorking, this);
string log = "start thread: " + to_string((unsigned int)threads[i]);
}
// 创建并启动管理者线程
pthread_create(&(adjust_tid), NULL, threadManager, this);
} while (0);
}
(2)添加任务
void ThreadPool::addPoolTask(Task & task)
{
/*添加任务到任务队列里*/
task_queue->addTask(task);
/*添加完任务后,队列不为空,唤醒线程池中 等待处理任务的线程*/
pthread_cond_signal(&queue_not_empty);
}
(3)管理者线程的回调函数
- 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时。
- 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且 存活的线程数 大于 最小线程数时。
void * ThreadPool::threadManager(void * arg)
{
ThreadPool *pool = (ThreadPool*)arg;
while (true)
{
sleep(DEFAULT_TIME); /*定时 对线程池管理*/
pthread_mutex_lock(&(pool->lock));
size_t queue_size = pool->task_queue->taskNumber(); /* 关注 任务数 */
int live_thr_num = pool->live_thr_num; /* 存活 线程数 */
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num; /* 忙着的线程数 */
pthread_mutex_unlock(&(pool->thread_counter));
/* 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时 如:30>=10 && 40<100*/
if (queue_size >= MIN_WAIT_TASK_NUM &&
live_thr_num < pool->max_thr_num)
{
pthread_mutex_lock(&(pool->lock));
int add = 0;
/*一次增加 DEFAULT_THREAD 个线程*/
for (int i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
&& pool->live_thr_num < pool->max_thr_num; i++)
{
if (pool->threads[i] == 0 || !pool->threadIsAlive(pool->threads[i]))
{
pthread_create(&(pool->threads[i]), NULL, threadWorking, arg);
add++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->lock));
}
/* 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且 存活的线程数 大于 最小线程数时*/
if ((busy_thr_num * 2) < live_thr_num && live_thr_num > pool->min_thr_num)
{
/* 一次销毁DEFAULT_THREAD个线程, 隨機10個即可 */
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY; /* 要销毁的线程数 设置为10 */
pthread_mutex_unlock(&(pool->lock));
for (int i = 0; i < DEFAULT_THREAD_VARY; i++)
{
/* 通知处正在等待的线程, 他们会自行终止*/
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return nullptr;
}
(4)工作线程线程回调函数
- 循环。查看任务队列此时有没有任务,没有就阻塞在条件变量。
- 如果有需要被删除的线程,退出当前线程。
- 获取任务并执行。
void * ThreadPool::threadWorking(void * arg)
{
ThreadPool *pool = (ThreadPool *)arg;
while (true) {
/* Lock must be taken to wait on conditional variable */
/*刚创建出线程,等待任务队列里有任务,否则阻塞等待任务队列里有任务后再唤醒接收任务*/
pthread_mutex_lock(&(pool->lock));
/*task number == 0 说明没有任务,调 wait 阻塞在条件变量上, 若有任务,跳过该while*/
while (pool->task_queue->taskNumber() == 0)
{
string log = "thread:" + to_string((unsigned int)pthread_self()) + " is waiting";
pool->m_log->Log(log, __FILE__, __LINE__);
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
/*清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程*/
if (pool->wait_exit_thr_num > 0)
{
pool->wait_exit_thr_num--;
/*如果线程池里线程个数大于最小值时可以结束当前线程*/
if (pool->live_thr_num > pool->min_thr_num)
{
log = "thread:" + to_string((unsigned int)pthread_self()) + " is exiting";
pool->m_log->Log(log, __FILE__, __LINE__);
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
pthread_mutex_unlock(&(pool->lock));
/*从任务队列里获取任务, 是一个出队操作*/
Task task = pool->task_queue->takeTask();
/*执行任务*/
string log = "thread:" + to_string((unsigned int)pthread_self()) + " is start working...";
pool->m_log->Log(log, __FILE__, __LINE__);
pthread_mutex_lock(&(pool->thread_counter)); /*忙状态线程数变量琐*/
pool->busy_thr_num++; /*忙状态线程数+1*/
pthread_mutex_unlock(&(pool->thread_counter));
// 执行处理动作
(*(task.function))(task.arg); /*执行回调函数任务*/
//task.function(task.arg); /*执行回调函数任务*/
/*任务结束处理*/
log = "thread:" + to_string((unsigned int)pthread_self()) + " is end working...";
pool->m_log->Log(log, __FILE__, __LINE__);
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--; /*处理掉一个任务,忙状态数线程数-1*/
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
return nullptr;
}