1.线程池的概念
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着 监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利 用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。我们去处理任务时,一个任务对应一个创建一个线程进行处理,效率是比较低的。我们可以预先创建一批线程,任务队列里没有任务的时候,每个线程都休眠,当队里中有任务的时候,就可以环形线程进行处理了。唤醒线程的成本比创建整个线程的成本小,这就是线程池的逻辑思想
2.线程池的使用场景
-- 需要大量的线程来完成任务,且完成任务的时间比较短。
-- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
-- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
3.实现线程池
一.线程的封装
Thread.hpp
Thread类主要成员变量是线程名,函数,线程参数,参数ID以及对应编号
Thread类提供了一个无参构造,完成对成员变量name的赋值。同时,对外主要提供了start接口和join接口,对于join接口就是线程等待,而对于start接口就是创建线程的接口,在外部如果调用的话我们需要传入对应的函数以及线程对应的参数。
#pragma once
#include <pthread.h>
#include <iostream>
#include <cassert>
#include <string>
#include <functional>
namespace ThreadNs
{
typedef std::function<void*(void*)> func_t;
const int num =1024;
class Thread
{
private:
static void* start_routine(void*args)
{
Thread* _this = static_cast<Thread*>(args);
return _this->callback();
}
public:
Thread()
{
char namebuffer[num];
snprintf(namebuffer,sizeof namebuffer,"thread-%d",threadnum++);
name_ = namebuffer;
}
void start(func_t func,void*args = nullptr)
{
func_ = func;
args_ = args;
int n = pthread_create(&tid_,nullptr,start_routine,this);
assert(n==0);
(void)n;
}
void join()
{
int n = pthread_join(tid_,nullptr);
assert(n==0);
(void)n;
}
std::string threadname()
{
return name_;
}
~Thread()
{}
void* callback()
{
return func_(args_);
}
private:
std::string name_;
func_t func_;
void *args_;
pthread_t tid_;
static int threadnum;
};
int Thread::threadnum = 1;
}
对于任务队列,可以由多个线程进行访问所以要加锁保护,下面是对锁的封装:
LockGuard.hpp:
#include <iostream>
#include <mutex>
class Mutex
{
public:
Mutex(pthread_mutex_t*lock_p=nullptr)
:lock_p_(lock_p)
{}
void lock()
{
if(lock_p_) pthread_mutex_lock(lock_p_);
}
void unlock()
{
if(lock_p_) pthread_mutex_unlock(lock_p_);
}
~Mutex()
{}
private:
pthread_mutex_t * lock_p_;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t*mutex)
:mutex_(mutex)
{
mutex_.lock();
}
~LockGuard()
{
mutex_.unlock();
}
private:
Mutex mutex_;
};
创建一批线程时,我们需要实现线程的调用函数static void*handlerTask,之所以是静态的,是因为我们要把这个运行函数传递给Thread类中的func_,不能有this指针,所以是静态成员函数。而没有this指针,我们无法访问ThreadPool里面的成员变量,所以需要封装接口供其调用。
ThreadPool.hpp
#pragma once
#include "Thread.hpp"
#include "LockGuard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <mutex>
#include <unistd.h>
using namespace ThreadNs;
const int gnum = 3;
template <class T>
class ThreadPool;
template <class T>
class ThreadData
{
public:
ThreadPool<T> *threadpool;
std::string name;
public:
ThreadData(ThreadPool<T> *tp, const std::string &n) : threadpool(tp), name(n)
{ }
};
template <class T>
class ThreadPool
{
private:
static void *handlerTask(void *args)
{
ThreadData<T> *td = (ThreadData<T> *)args;
ThreadPool<T> *threadpool = static_cast<ThreadPool<T> *>(args);
while (true)
{
T t;
{
LockGuard lockguard(td->threadpool->mutex());
while(td->threadpool->isQueueEmpty())
{
td->threadpool->threadWait();
}
t = td->threadpool->pop();
}
std::cout << td->name << " 获取了一个任务" << t.toTaskString() << "并处理完成,结果是: " << t() << std::endl;
}
delete td;
return nullptr;
}
public:
void lockQueue() { pthread_mutex_lock(&_mutex); }
void unlockQueue() { pthread_mutex_unlock(&_mutex); }
bool isQueueEmpty() { return _task_queue.empty(); }
void threadWait() { pthread_cond_wait(&_cond, &_mutex); }
T pop()
{
T t = _task_queue.front();
_task_queue.pop();
return t;
}
void Push(const T &in)
{
LockGuard lockguard(&_mutex);
_task_queue.push(in);
pthread_cond_signal(&_cond);
}
pthread_mutex_t *mutex()
{
return &_mutex;
}
public:
ThreadPool(const int &num = gnum) : _num(num)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
for (int i = 0; i < _num; i++)
{
_threads.push_back(new Thread());
}
}
void run()
{
for (const auto &t : _threads)
{
ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
t->start(handlerTask, td);
std::cout << t->threadname() << "start..." << std::endl;
}
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for (const auto &t : _threads)
delete t;
}
private:
int _num;
std::vector<Thread *> _threads;
std::queue<T> _task_queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
};
因为线程池中存储的任务是任意的,所以需要将线程池进行模板化,下面引入任务组件Task.hpp
#pragma once
#include <iostream>
#include <functional>
class Task
{
using func_t = std::function<int(int,int ,char)>;
public:
Task(){}
Task(int x,int y,char op,func_t func)
:_x(x),_y(y),_op(op),_callback(func)
{}
std::string operator()()
{
int result = _callback(_x,_y,_op);
char buffer[1024];
snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
return buffer;
}
std::string toTaskString()
{
char buffer[1024];
snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
return buffer;
}
private:
int _x;
int _y;
char _op;
func_t _callback;
};
const std::string oper = "+-*/%";
int mymath(int x,int y,char op)
{
int result = 0;
switch(op)
{
case '+':
result = x+y;
break;
case '-':
result = x-y;
break;
case '*':
result = x*y;
break;
case '/':
if(y==0)
{
std::cerr<<"div zero error!"<<std::endl;
result = -1;
}
else
{
result = x/y;
}
break;
case '%':
if(y==0)
{
std::cerr<<"mod zero error!"<<std::endl;
result = -1;
}
else
{
result = x%y;
}
break;
default:
break;
}
return result;
}
main.cpp
#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <ctime>
int main()
{
ThreadPool<Task>* tp = new ThreadPool<Task>();
tp->run();
srand(time(0));
int x,y;
char op;
while(true)
{
x = rand()%10+1;
y = rand()%20+1;
op =oper[rand()%oper.size()];
Task t(x,y,op,mymath);
tp->Push(t);
sleep(1);
}
return 0;
}