什么是线程池?在学STL接口的时候我们发现很多的结构都是有自动的扩容机制的,但频繁的扩容会有一定的代价,就好比我每次需要一块钱的时候每次都只向爸爸要一块钱,而爸爸在距离我500m远的距离,那么当我总共需要10块钱的时候就要来回跑10趟,十分耗时间,那么我不如直接一次性跟他要10块钱,就算有多的我也就先放在身上,有需要的时候就可以直接使用。内存池就是一次性去跟系统申请一大块内存空间来减少申请内存的次数。
与内存池类似,创建线程也是需要一定的代价的,因此我们也可以采用一次性申请多个线程的方式来提高效率。我们接下来的代码希望能够实现如下的功能
namespace ssj_thread_pool
{
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:
// 将lock,unlock,wait,wakeup,isempty进行封装不但使用
// 方便而且因为Routine是静态的,所以无法访问类内私有成员(mtx
// ,cond,taskqueue)将他们封装可以解决这个问题。
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();
}
// 在类内的成员函数会被默认传递this指针,因此需要使用静态成员函数
static void* Routine(void *args)
{
// 线程分离,无需再进行等待
pthread_detach(pthread_self());
ThreadPool<T> *tp = (ThreadPool<T>*)args;
while (true)
{
tp->Lock();
// 使用循环判断防止伪唤醒
while (tp->IsEmpty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
// 将执行任务放在锁外进行,这样可以让其
// 它线程在任务执行时继续从任务队列读取
// 任务达到多任务并发执行的效果
t();
}
}
public:
void InitThreadPool()
{
for (int i = 0; i < _num; i++)
{
pthread_t tid;
/* 将对象地址传给Routine,因为Routine是
静态成员函数,没有this指针,所以不能访问类
内成员,因此将this指针传给它*/
pthread_create(&tid, nullptr, Routine, (void*)this);
sleep(1);
}
}
void PushTask(const T &in)
{
Lock();
_task_queue.push(in);
Unlock();
WakeUp();
}
void PopTask(T *out)
{
*out = _task_queue.front();
_task_queue.pop();
}
ThreadPool(int num = g_num)
: _num(num)
{
pthread_mutex_init(&_mtx, nullptr);
pthread_cond_init(&_cond, nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&_mtx);
pthread_cond_destroy(&_cond);
}
};
}
我们创建的任务就是用x和y来执行op对应的±*/%运算
namespace ssj_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 << "Please choose another option" << std::endl;
break;
}
std::cout << "当前任务正在被:" << pthread_self() << "处理" \
<< _x << _op << _y << "=" << res << std::endl;
return res;
}
int operator()()
{
return Run();
}
~Task() {}
};
}
int main()
{
ThreadPool<Task> *tp = new ThreadPool<Task>();
tp->InitThreadPool();
srand((long long)time(nullptr));
while(true)
{
Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
tp->PushTask(t);
}
return 0;
}
单例模式是一种经典的设计模式。对于某些类只应该具有一个对象,就称之为单例。我们定义对象时实际上需要两个步骤:
设计模式可以看作是一种经验,根据一些经典的场景,给定了一些特定的解决方案,这些解决方案就是设计模式
就像不同的同学写暑假作业会选择在不同的时间一样,有的同学可能会 在领到暑假作业后前几天就把暑假作业完成了,这种方式就称为饿汉方式,而有的同学心理承受能力极强,对于堆积如山的作业嗤之以鼻,相信自己能够在开学之前的那个晚上把他们轻松完成,这种方式就称为懒汉方式。不难看出对于第一类同学来说,他们在假期开始的前几天可能会比较痛苦,但是后面就会玩的很开心,而第二类同学则恰好相反,在假期开始时他们可以玩的很爽,但是在最后他们也会经历补作业的痛苦。
懒汉方式最核心的思想是“延时加载”,从而能够优化服务器的启动速度。写时拷贝就是懒汉方式的一个例子。
我们之前写的线程池其实就是一个单例,因为我们只需要一个线程池就够了,如果我们需要更多的线程,那么直接创建更多的线程就好了。因此接下来我们就试着用懒汉方式来修改一下我们线程池的代码。
namespace ssj_thread_pool
{
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;
static ThreadPool<T> *_ins;
private:
// 构造函数必须实现,且必须私有
ThreadPool(int num = g_num)
: _num(num)
{
pthread_mutex_init(&_mtx, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool<T> &tp) = delete;
ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;
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();
}
static void *Routine(void *args) // 在类内的成员函数会被默认传递this指针,因此需要使用静态成员函数
{
pthread_detach(pthread_self());
ThreadPool<T> *tp = (ThreadPool<T> *)args;
while (true)
{
tp->Lock();
while (tp->IsEmpty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t();
}
}
public:
static ThreadPool<T> *GetInstance()
{
static pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;
// 当前单例对象还没有被创建
if (_ins == nullptr) // 申请锁的代价较高,因此使用双判断,减少锁的征用
{
pthread_mutex_lock(&_lock);
if (_ins == nullptr)
{
_ins = new ThreadPool<T>();
_ins->InitThreadPool();
std::cout << "首次加载对象" << std::endl;
}
}
pthread_mutex_unlock(&_lock);
return _ins;
}
void InitThreadPool()
{
for (int i = 0; i < _num; i++)
{
pthread_t tid;
pthread_create(&tid, nullptr, Routine, (void *)this);
sleep(1);
}
}
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);
}
};
template <class T>
ThreadPool<T> *ThreadPool<T>::_ins = nullptr;
}