移动网站建设哪家便宜,跨境电商saas,wordpress+经典推荐,个人建设网站需要什么证件吗目录标题 什么是线程池线程的封装准备工作构造函数和析构函数start函数join函数threadname函数完整代码 线程池的实现准备工作构造函数和析构函数push函数pop函数run函数完整的代码 测试代码 什么是线程池
在实现线程池之前我们先了解一下什么是线程池#xff0c;所谓的池大家… 目录标题 什么是线程池线程的封装准备工作构造函数和析构函数start函数join函数threadname函数完整代码 线程池的实现准备工作构造函数和析构函数push函数pop函数run函数完整的代码 测试代码 什么是线程池
在实现线程池之前我们先了解一下什么是线程池所谓的池大家可以理解为一次性申请和创建很多的东西然后将其保存起来等未来需要的时候就不需要再创建和申请了直接从保存的地方拿就行了在生活中我们也经常用到池化技术比如说去菜场里面买菜并不是只卖一顿的菜而是买好几天要吃的菜因为这么做的话可以大幅度的减少去菜场所带来的时间消耗那么在计算机中也是同样的道理比如说使用函数new向操作系统申请10字节的空间时操作系统并不是真的从内存中给你现场申请10字节的空间而是当前的进程早就提前申请好了一大堆空间把这部分空间保存下来一旦你再申请的话将该空间的一部分给你就行并不会立马跑到内存上进行申请这样做可以有效的提高程序的效率那么这就是一个池化技术那么这里的线程池也是同样的道理我们可以先创建多个线程并将其保存下来然后再创建一个容器用来接收任务一旦有任务了就自动唤醒对应的线程将其进行处理即可无需再次创建比如说下面的图片 那么接下来我们将一步一步的实现线程池首先来完成线程的封装。
线程的封装
准备工作
因为创建线程需要对应的执行函数和参数以及对应的pid_t变量所以类中得有一个function对象和一个void*类型的指针和一个pid_td对象
typedef std::functionvoid *(void *) func_t;class Thread
{public:private:func_t func_;void *args_;pthread_t tid_;};此外我们还希望该类能够有一个自己的名字所以类中还有一个string的对象用来记录线程的名字这里我们希望名字的形式为thread 1,thread 2…以此类推所以我们在类中我们再添加一个静态的整形变量用来记录当前是创建的第几个线程所以当前类的代码如下
typedef std::functionvoid *(void *) func_t;class Thread
{public:private:std::string name_;func_t func_;void *args_;pthread_t tid_;static int threadnum;};int Thread::threadnum 1;构造函数和析构函数
对于该类的设计我们是这样向的创建类的时候并不创建对应的线程执行函数这个交给后面的start函数构造函数要干的事情就是将该线程对应的名字创建好即可那么这里创建一个char类型的数组然后使用snprintf函数将对应的名字输入到这个数组里面最后用这个数组的起始的地址构造string对象即可那么这里的代码如下
Thread()//构造函数初始化名字
{char namebuffer[num];//这里的num为全局变量后面会写snprintf(namebuffer, sizeof namebuffer, thread-%d, threadnum);name_ namebuffer;
}对于析构函数当前没有什么内容需要我们手动释放所以析构函数什么都不用干
~Thread()
{// do nothing
}start函数
start函数干的事情就是使用pthread_start函数创建线程执行对应的函数因为pthread_start函数需要调用对象以及函数的参数所以strat函数需要两个参数并将两个参数的值赋值给类中对应的对象
void start(func_t func, void *args nullptr)//start函数开始创建线程执行任务
{func_func;args_args;
}然后我们就应该调用函数pthread_create来创建线程这里本可以直接使用类内对象func_和args来完成线程的创建但是我们这里另寻一路来带着大家回顾更多的知识我们创建一个名为start_routine的类内函数通过让pthread_create函数调用这个该来实现func_函数对象的调用 void *start_routine(void *args)
{}我们知道pthread_create函数对要调用的函数是有要求的要求它只有一个参数并且函数的返回值和参数的类型都得是void*,但是类内函数有一个特点就是自动的隐藏了一个this指针也就是说上面的start_routine函数看上去只有一个参数实际上他有两个所以这是不符合pthread_create函数的要求的那么这里的解决办法就是将该函数改成静态函数这样就没有了this指针但是这种做法又带来了另外一个问题就是func_是类内的非静态对象静态函数是没有资格调用他的所以为了解决这个问题我们在pthread_create函数传递参数的时候就传递一个this指针过去这样静态函数就可以通过这个this指针访问非静态的成员或者函数
static void *start_routine(void *args)
{Thread *_this static_castThread *(args);
}
void start(func_t func, void *args nullptr)//start函数开始创建线程执行任务
{func_func;args_args;int n pthread_create(tid_, nullptr, start_routine, this);assert(n 0); (void)n;
}可是这里还存在一个问题func_对象是私有的通过类对象是访问不到的所以这里我们还得创建一个接口函数用来访问func_对象比如说下面的代码
void *callback()
{return func_(args_);
}//该函数调用类内的函数对象然后就可以通过this指针调用这个callback函数调用func_对象
void *callback()
{return func_(args_);
}//该函数调用类内的函数对象
static void *start_routine(void *args)
// 参数为指向该类对象的this指针所以可以访问到类内部的callback函数
{Thread *_this static_castThread *(args);return _this-callback();
}
void start(func_t func, void *args nullptr)//start函数开始创建线程执行任务
{func_func;args_args;int n pthread_create(tid_, nullptr, start_routine, this); assert(n 0); (void)n;
}那么这就是start函数的实现过程。
join函数
该函数的实现就非常的简单直接调用pthread_join函数即可然后创建一个变量记录一下返回值并判断返回值是否为0
void join()
{int n pthread_join(tid_, nullptr);assert(n 0);(void)n;
}threadname函数
该函数的作用就是返回线程的名字所以该函数的实现就直接返回类内的string对象即可那么这里的代码如下
string threadname()
{return name_;
}
完整代码
#include iostream
#include string
#include cstring
#include cassert
#include functional
#include pthread.h
using namespace std;namespace ThreadNs
{typedef std::functionvoid *(void *) func_t;const int num 1024;class Thread{private:// 在类内创建线程想让线程执行对应的方法需要将方法设置成为staticstatic void *start_routine(void *args) // 参数为指向该类对象的this指针所以可以访问到类内部的callback函数{Thread *_this static_castThread *(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)//start函数开始创建线程执行任务{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;}string threadname(){return name_;}~Thread(){// do nothing}void *callback() { return func_(args_);}//该函数调用类内的函数对象private:std::string name_;func_t func_;void *args_;pthread_t tid_;static int threadnum;};int Thread::threadnum 1;
} // end namespace ThreadNs线程池的实现
准备工作
既然是线程池那么肯定得有一个变量用来记录池内线程的个数还得有一个数组用来存储创建出来的Thread对象的指针因为线程要获取任务所以我们还得添加一个队列用来存储待处理的任务因为在获取数据和存放数据的过程中可能会遇到多线程所带来的线程安全问题所以得添加锁变量又因为存放数据和获取数据的过程是互斥的所以这个锁变量只能有一个那么当前的代码就如下
templateclass T
class ThreadPool
{
public:private:int _num;//表示线程池中线程的个数queueT _task_queue;//装载任务的队列vectorThread* _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};构造函数和析构函数
构造函数只有一个参数用来表示当前的内存池有多少个线程并将该参数赋值给_num然后在函数里面对条件变量和锁进行初始化然后创建一个循环不断的创建Thread对象并将该对象的地址尾差到vector中
ThreadPool(int num5)
:_num(num)
{pthread_mutex_init(_mutex,nullptr);pthread_cond_init(_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i0;inum;i){_threads.push_back(new Thread());}
}析构函数干的事情就是将条件变量和锁变量进行销毁然后通过for循环将vector对象中指针指向的空间进行回收即可那么这里的代码如下
~ThreadPool()
{pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);for(const auto t:_threads){delete t;}
}push函数
该函数的作用就是往任务队列中查入数据所以该函数需要一个const T类型的参数
void push(const Tin)
{
}因为插入数据的过程可能会出现线程安全问题所以函数的第一步就是先对其进行枷锁然后调用队列中的push函数将参数插入到队列里面此时的队列中肯定有数据所以在此之后就使用pthread_cond_signal函数将条件变量上等待的线程进行唤醒最后解锁即可
void push(const Tin)
{pthread_mutex_lock(_mutex);_task_queue.push(in);pthread_cond_signal(_cond);pthread_mutex_unlock(_mutex);
}pop函数
pop函数的作用是获取队列中的数据并删除因为该函数是线程函数调用的而多个线程在获取任务的时候要通过锁来保持互斥的关系所以调用该函数的时候不用担心线程安全问题所以在函数里面创建一个变量用来保存队列中的首元素数据并将其该数据删除最后返回该变量即可
T pop()
{T t_task_queue.front();_task_queue.pop();return t;
}run函数
run函数的功能就是将线程池中的每个线程都运行起来从队列中获取任务那么这个时候就可以通过for循环的形式来调用Thread对象中的start函数来实现因为start函数需要一个函数指针和一个参数所以这里我们还得创建一个指定形式的函数并且该函数还得是static类型
static void* handlerTask(void*args)
{}在这个函数里面我们就可以实现从队列中获取任务并执行所以该函数里面肯定得访问到类中的其他数据所以我们传递给该函数的参数就是ThreadPool类型的this指针但是这里我们还想实现一个功能就是在执行对应任务的时候想知道是哪个线程所执行的也就是知道对应线程的名字所以按设想来说这里应该将对象的名字也传递给handlerTask函数但是该函数只有一个参数传递了名字就传递不了this指针所以这里我们的做法就是再创建一个类该类中有一个string对象用来存储名字和一个ThreadPool类型的指针用来访问类中的其他资源那么这里的代码如下
templateclass T
class ThreadPool;
templateclass T
class ThreadDate
{
public:ThreadPoolT* threadpool;string name;ThreadDate(ThreadPoolT*tp,const string n):name(n),threadpool(tp){}
};
templateclass T
class ThreadPool
static void* handlerTask(void*args)
{}
public:
ThreadPool(int num5)
:_num(num)
{pthread_mutex_init(_mutex,nullptr);pthread_cond_init(_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i0;inum;i){_threads.push_back(new Thread());}
}
void run()
{}
~ThreadPool()
{pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);for(const auto t:_threads){delete t;}
}
private:int _num;//表示线程池中线程的个数queueT _task_queue;//装载任务的队列vectorThread* _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};那么在run函数里面要干的事情就是new一个ThreadDate对象,然后讲该对象的地址作为参数传递给start函数并打印一句话用来表明当前的线程已经运转起来了
void run()
{//这个函数就是将每个线程都启动for(const autot:_threads){ThreadDateT*td new ThreadDateT(this,t-threadname());t-start(handlerTask,td);cout t-threadname() start... endl;}
}虽然handlerTask函数拥有了this指针但是锁变量和条件变量等等都是私有的外界是无法访问到的所以这里还得添加一些接口函数用来访问这些成员变量
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); }//把线程放到条件变量上进行等待有了这些函数之后handlerTask函数就可以实现了首先将参数的类型进行转换然后使用lockQueue函数进行枷锁枷锁成功之后就创建一个循环使用isQueueEmpty函数来判断当前的队列中是否有任务如果没有的话就使用threadWait函数将该线程挂起
static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。
{ThreadDateT* tdstatic_castThreadDateT*(args);while(true){td-threadpool-lockQueue();while(td-threadpool-isQueueEmpty()){td-threadpool-threadWait();}
}while循环结束就表明当前的队列存在数据所以此时就可以通过 td中的成员变量访问到pop函数从而获取到队列中的任务因为任务的执行并不会收到多线程之间的影响所以得到任务之后就可以使用unlockQueue函数进行解锁然后执行任务并打印执行之后的结果最外层的while循环结束之后就可以使用delete销毁td指针指向的对象并返回nullptr
static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。{ThreadDateT* tdstatic_castThreadDateT*(args);while(true){td-threadpool-lockQueue();while(td-threadpool-isQueueEmpty()){td-threadpool-threadWait();}T ttd-threadpool-pop();td-threadpool-unlockQueue();string resultt();couttd-name 处理了任务 t.toTaskString() 处理的结果为resultendl;}delete td;return nullptr;}完整的代码
#includeiostream
#includevector
#includeThread.hpp
#includeLockGuard.hpp
#includeTask.hpp
#includequeue
#includestring
#includeunistd.h
using namespace std;
using namespace ThreadNs;
templateclass T
class ThreadPool;
templateclass T
class ThreadDate
{
public:ThreadPoolT* threadpool;string name;ThreadDate(ThreadPoolT*tp,const string n):name(n),threadpool(tp){}
};
templateclass T
class ThreadPool
{static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。{ThreadDateT* tdstatic_castThreadDateT*(args);while(true){td-threadpool-lockQueue();while(td-threadpool-isQueueEmpty()){td-threadpool-threadWait();}T ttd-threadpool-pop();td-threadpool-unlockQueue();string resultt();couttd-name 处理了任务 t.toTaskString() 处理的结果为resultendl;}delete td;return nullptr;}
public:ThreadPool(int num5):_num(num){pthread_mutex_init(_mutex,nullptr);pthread_cond_init(_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i0;inum;i){_threads.push_back(new Thread());}}void run(){//这个函数就是将每个线程都启动for(const autot:_threads){ThreadDateT*td new ThreadDateT(this,t-threadname());t-start(handlerTask,td);cout t-threadname() start... endl;}}T pop(){T t_task_queue.front();_task_queue.pop();return t;}~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);for(const auto t:_threads){delete t;}}void push(const Tin){pthread_mutex_lock(_mutex);_task_queue.push(in);pthread_cond_signal(_cond);pthread_mutex_unlock(_mutex);}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); }
private:int _num;//表示线程池中线程的个数queueT _task_queue;//装载任务的队列vectorThread* _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};测试代码
我们可以用下面的代码来进行测试
#includeiostream
#includeThreadPool.hpp
using namespace std;
int main()
{ThreadPoolTask* tpnew ThreadPoolTask();tp-run();int x, y;char op;while(1){std::cout 请输入数据1# ;std::cin x;std::cout 请输入数据2# ;std::cin y;std::cout 请输入你要进行的运算#;std::cin op;Task t(x, y, op, mymath);std::cout 你刚刚录入了一个任务: t.toTaskString() 确认提交吗[y/n]# ;char confirm;std::cin confirm;if(confirm y) tp-push(t);sleep(1);}
}代码的运行结果如下 可以看到符合我们的预期那么这就是实现的过程。