一,线程池的作用和优点
线程池使用的是一种池化技术,当我们要使用线程时采用线程池创建就一次创建多个线程,在调用当前线程时就让其它的线程进行等待。这样做的优点有如下几点:
1,提高响应速度。线程池提前把线程创建好线程,这样能够在需要调度线程时直接调用而节省创建线程所需要的时间。
2,节省线程资源的消耗,线程池可以通过复用的方式来使用这些线程来执行一些任务。这样便可以节省一些线程资源的消耗。
3,进行统一的调度和分配,。线程是稀缺资源,线程如果被无限的创建就会导致系统的不稳定性。线程池则可以对线程进行统一的调度和分配,避免线程的无限创建。
二,线程池的创建
1,线程池的成员
我们实现的线程池其实就是一个类,既然是类,这个类里面就会有线程池的创建过程中所需要的一些成员。线程池中的成员如下:
1,管理线程的对象:这里采用vector数组的方式实现。
2,任务队列:线程池中的是用来解决任务的,所以我们要有一个地方来接收任务。
3,锁:在多线程并发执行时很大概率上会存在线程安全的问题,所以我们要有一个锁来保证线程安全。
4,条件变量:任务的处理和存放都是有限制的。当任务被处理完了以后便不可以再处理任务了,这个时候这个进来的线程就要等待。同样的,当任务队列堆放满了以后也不能再堆放了所以进来的线程也要等待。
代码如下:
struct TDINFO//线程的信息结构体
{
pthread_t td;
string name;
};
class pthreadpoll
{
private:
vector<TDINFO> tds_; // 管理线程的数组
queue<T> q_; // 任务队列
pthread_mutex_t lock_; // 锁
pthread_cond_t cond_; // 条件变量
int max_cp_; // 任务队列的最大容量
};
2,线程池中的函数
1,构造函数
在线程池中,这个构造函数的作用就是初始化。初始化的对象为:
1.lock_ 2,cond_
代码:
pthreadpoll() // 构造函数,主要用于初始化锁和条件变量
{
pthread_mutex_init(&lock_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
2,析构函数
析构函数的作用也很简单,就是用来对变量lock_ 和cond_进行销毁回收。这里不用对q和threads进行销毁,因为这两个时stl容器有自己的析构函数。
代码:
~pthreadpoll()
{
pthread_mutex_destroy(&lock_);
pthread_cond_destroy(&cond_);
}
3,开始创建线程函数
这个函数的作用便是用户通过调用这个函数来创建想要创建的对应数量的线程。
代码如下:
void start(int max_cp = defaultnum) // 开始创建线程
{
for (int i = 1; i <= max_cp; i++)
{
pthread_t tid;
pthread_create(&tid, nullptr, Task, this); // 创建线程,这里的第第四个参数传入的是this指针因为是在类内,Task函数要被写成静态的
TDINFO Td;
string name = "thread-" + to_string(i);
Td.name = name;
Td.td = tid;
tds_.push_back(Td); // 队列
}
}
这里会有一个默认的数量:
const int defaultnum = 5;
这里还会有一个Task的类,代表一个任务,也就是这个线程要执行的函数
代码如下:
static void *Task(void *args)
{
pthreadpoll<T> *td = static_cast<pthreadpoll<T> *>(args); // 将this指针转换回来
// 执行任务
while (true)
{
// 1.先锁住
td->Lock();
// 2,判断当前的队列是否为空
while (td->Isempty())
{
td->Sleep(); // 进入条件变量进行等待,使用while循环判断防止虚假唤醒
}
// 3,取任务
T task = td->q_.front();
td->q_.pop();
td->Wakeup(); // 唤醒线程,唤醒下一个线程来执行任务。
td->Unlock();
// 4,做任务
task.run();
// 5,显示结果
cout << task.Get_result() << endl;
}
}
在实现这个任务时要注意的点如下:
1,这个函数要实现为静态函数,因为线程创建的第三个参数的格式必须是void*(void*)形式的。如果再类内不实现为静态形式则这个函数的第一个t参数为his指针。
2,第四个参数必须传入this,因为静态函数只能调用静态成员。传入this指针以后才能调用类内的方法和成员。
4,放入任务函数
这个函数能够让用户往任务队列里面放入任务。
代码如下:
void push(const T &in)//外面的用户通过该函数来放入要处理的任务
{
q_.push(in);
Wakeup();//唤醒线程执行任务
sleep(1);
}
5,拿出任务函数
该函数的任务便是拿出一个任务。
代码如下:
T pop()
{
T front = q_.front();
q_.pop();
return front;
}
这个函数在Task函数中被调用。
三,线程池源码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "Task.hpp"
using namespace std;
const int defaultnum = 5;
struct TDINFO
{
pthread_t td;
string name;
};
template <class T> // 实现成模板的类
class pthreadpoll
{
public:
void Lock() // 封装加锁
{
pthread_mutex_lock(&lock_);
}
void Unlock() // 封装解锁
{
pthread_mutex_unlock(&lock_);
}
bool Isempty()//封装队列的判空函数
{
return q_.empty();
}
void Wakeup()//封装条件变量的唤醒函数
{
pthread_cond_signal(&cond_);
}
void Sleep()//封装条件变量的等待函数
{
pthread_cond_wait(&cond_, &lock_);
}
pthreadpoll() // 构造函数,主要用于初始化锁和条件变量
{
pthread_mutex_init(&lock_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
T pop()
{
T front = q_.front();
q_.pop();
return front;
}
static void *Task(void *args)
{
pthreadpoll<T> *td = static_cast<pthreadpoll<T> *>(args); // 将this指针转换回来
// 执行任务
while (true)
{
// 1.先锁住
td->Lock();
// 2,判断当前的队列是否为空
while (td->Isempty())
{
td->Sleep(); // 进入条件变量进行等待,使用while循环判断防止虚假唤醒
}
// 3,取任务
T task = td->q_.front();
td->q_.pop();
td->Wakeup(); // 唤醒线程
td->Unlock();
// 4,做任务
task.run();
// 5,显示结果
cout << task.Get_result() << endl;
}
}
void start(int max_cp = defaultnum) // 开始创建线程
{
for (int i = 1; i <= max_cp; i++)
{
pthread_t tid;
pthread_create(&tid, nullptr, Task, this); // 创建线程,这里的第第四个参数传入的是this指针因为是在类内,Task函数要被写成静态的
TDINFO Td;
string name = "thread-" + to_string(i);
Td.name = name;
Td.td = tid;
tds_.push_back(Td); // 队列
}
}
void push(const T &in)
{
q_.push(in);
Wakeup();
sleep(1);
}
~pthreadpoll()
{
pthread_mutex_destroy(&lock_);
pthread_cond_destroy(&cond_);
}
private:
vector<TDINFO> tds_; // 管理线程的数组
queue<T> q_; // 任务队列
pthread_mutex_t lock_; // 锁
pthread_cond_t cond_; // 条件变量
int max_cp_; // 任务队列的最大容量
};