线程池概念
提前准备好的线程,用来随时处理任务,就称之为线程池!
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池应用场景
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
线程池说明
线程池的实现
// task.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ns_task
{
class Task
{
private:
int x_;
int y_;
char op_; // +-*/%
public:
Task() {}
Task(int x, int y, char op) : x_(x), y_(y), op_(op)
{
}
std::string Show()
{
std::string message = std::to_string(x_);
message += op_;
message += std::to_string(y_);
message +="=?";
return message;
}
int Run()
{
int res = 0;
switch (op_)
{
case '+':
res = x_ + y_;
break;
case '-':
res = x_ - y_;
break;
case '*':
res = x_ * y_;
break;
case '/':
res = x_ / y_;
break;
case '%':
res = x_ % y_;
break;
default:
std::cout << "错误的运算" << std::endl;
break;
}
std::cout << "当前任务正在被[" << pthread_self() << "]处理" << x_ << op_ << y_ << "=" << res << std::endl;
std::cout << "-----------------------------" << std::endl;
return res;
}
int operator()()
{
return Run();
}
~Task() {}
};
} // namespace ns_task
//
//thread_pool.hpp
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h> // sleep()
#include <pthread.h>
namespace ns_threadpool
{
const int g_num = 5;
template <class T>
class ThreadPool
{
private:
int num_; // 这个线程池有多少个线程
std::queue<T> task_queue_; // 任务队列——这是一个临界资源
pthread_mutex_t mtx_;
pthread_cond_t cond_;
public:
void Lock()
{
pthread_mutex_lock(&mtx_);
}
void UnLock()
{
pthread_mutex_unlock(&mtx_);
}
void Wait()
{
pthread_cond_wait(&cond_,&mtx_);
}
void WakeUp()
{
pthread_cond_signal(&cond_);
}
bool IsEmpty()
{
return task_queue_.empty();
}
public:
ThreadPool(int num = g_num)
: num_(num)
{
pthread_mutex_init(&mtx_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
// 细节:在类中要让线程执行类的成员方法,是不可行的!!!!!!! InitThreadPool回调Routine
// 解决:必须让线程执行静态方法
static void *Routine(void *args)
{
pthread_detach(pthread_self());
ThreadPool<T> *tp = (ThreadPool<T> *)args;
while (true)
{
// 由于是静态方法,所以在函数内部是无法访问类内成员的
// 所以pthread_create中第四个参数要传递this指针
// if (task_queue_.empty())
// {
// wait();
// }
tp->Lock();
// 首先检测线程池中是否有任务
while (tp->IsEmpty()) //不用if判断,防止伪唤醒
{
// 任务队列为空的话,我们需要挂起等待
tp->Wait();
}
// 当前行,队列中一定是有任务的
T t;
tp->PopTask(&t);
tp->UnLock();
// 当前线程处理任务的时候,其他线程也可能在处理任务,所以run方法写在解锁之外
t();
}
}
void InitThreadPool()
{
pthread_t tid;
for (int i = 0; i < num_; i++)
{
pthread_create(&tid, nullptr, Routine, (void *)this);
}
}
void PushTask(const T &in)
{
Lock();
// 向任务队列中塞入任务
task_queue_.push(in);
UnLock();
// 当任务放进去了之后,就要唤醒线程
WakeUp();
}
void PopTask(T*out)
{
*out=task_queue_.front();
task_queue_.pop();
}
~ThreadPool()
{
pthread_mutex_destroy(&mtx_);
pthread_cond_destroy(&cond_);
}
};
}
//
// main.cc
#include "thread_pool.hpp"
#include "task.hpp"
#include <ctime>
#include <cstdlib>
using namespace ns_threadpool;
using namespace ns_task;
// 设计目标:主线程设计任务派发给线程池,由线程池中的线程来完成任务
int main()
{
ThreadPool<Task> * tp=new ThreadPool<Task>();
tp->InitThreadPool();
srand((unsigned int)time(nullptr));
while(true)
{
Task t(rand()%20+1,rand()%10+1,"+-*/%"[rand()%5]);
tp->PushTask(t);
sleep(1);
}
return 0;
}
结果展示: