【多线程】八、线程池
文章目录
- Ⅰ. 线程池的概念
- Ⅱ. 简单线程池模拟实现
- 💥ThreadPool.hpp(线程池类)
- main.cpp(主函数)
- ThreadData.hpp(封装线程池和名称的类)
- Thread.hpp(线程库封装类,之前写过的)
- Task.hpp(任务类与任务函数,生产者消费者模型时候写过)
- LockGuard.hpp(锁封装类和守卫锁类,之前写过的)
- Makefile
- 执行结果
- Ⅲ. 单例模式的线程池
- Ⅳ. STL和智能指针是不是线程安全的❓❓❓
- 一、对于STL
- 二、对于智能指针

Ⅰ. 线程池的概念
线程池是一种多线程的并发模型,它包含了一组线程,用于执行多个任务,通常将任务存储在队列或者数组中,并通过一定的调度算法来实现任务的执行。线程池可以提高应用程序的性能,减少线程的创建和销毁所带来的开销,并能更好地控制并发线程的数量,从而避免因线程过多而导致系统性能下降的问题。
虽说线程过多会带来调度开销,进而影响缓存局部性和整体性能,但线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 socket
等的数量。
🎏一个基本的线程池包含以下几个部分:
- 任务队列:用于存放待执行的任务。
- 线程池管理器:用于创建、销毁、管理线程池中的线程。
- 工作线程:用于执行任务。
- 线程同步机制:用于线程间的同步和通信。
💥线程池的工作流程如下:
- 当有任务需要执行时,先将任务加入任务队列中。
- 当线程池中有空闲线程时,从任务队列中取出一个任务分配给空闲线程执行。
- 当线程池中没有空闲线程时,新建一个线程执行任务。
- 当线程执行完任务后,如果线程池中有其他任务需要执行,则继续执行下一个任务,否则该线程就成为空闲状态,等待下一个任务的到来。
- 如果线程长时间处于空闲状态,线程池管理器可能会根据一定的策略销毁该线程,从而节省系统资源。
☢️线程池的应用场景:
-
需要大量的线程来完成任务,且完成任务的时间比较短。
WEB
服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet
连接请求,线程池的优点就不明显了。因为Telnet
会话时间比线程的创建时间大多了。 -
对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
-
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
虽然说线程池听起来复杂,但是其实本质还是一个生产者消费者模型,只是说我们现在要维护的是一个存储多线程的队列或者数组,让它们进行调度去完成任务!除此之外,我们还会在下面的实现当中用上我们之前封装好的线程库和互斥锁库,虽说我们可以直接使用 c++11
中的接口,但是这里为了了解实现原理,我们选择自己使用封装好的接口!
Ⅱ. 简单线程池模拟实现
线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现,线程池中的线程可以从阻塞队列中获取任务进行任务处理,当线程都处于繁忙状态时可以将任务加入阻塞队列中,等到其它的线程空闲后进行处理。
下面我们实现一个简单的线程池,主要是下面的功能(下面不使用阻塞任务队列来实现):
-
封装好线程池类之后,我们只需要在主函数中调用
put
函数进行任务的放置即可让线程池自动调度空闲线程去处理任务! -
需要添加任务的话可以在
Task.hpp
中进行声明定义!
下面给出程序的调用流程图,帮助理解:
💥ThreadPool.hpp(线程池类)
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "LockGuard.hpp"
#include "Thread.hpp"
#include "ThreadData.hpp"using namespace ThreadNS;
const int MAXCAP = 100; // 线程池最大线程数量template <class T>
class ThreadPool
{
public:ThreadPool(const int& maxcap = MAXCAP):_cap(maxcap){// 初始化工作pthread_cond_init(&_cond, nullptr);pthread_mutex_init(&_mutex, nullptr);for(int i = 0; i < _cap; ++i)_threads.push_back(new Thread());}// 启动所有线程的函数void run(){for(const auto& t : _threads){// 使用ThreadData类装载线程池和名称,方便后面打印ThreadData<T>* td = new ThreadData<T>(this, t->threadname());t->start(handlerTask, td); std::cout << t->threadname() << " start......" << std::endl;}}// 向任务队列中放置任务的接口void put(const T& in){// 队列的操作是线程不安全的,所以加锁LockGuard lock(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond); // 唤醒其中一个线程执行任务}// 拿取并且弹出任务队列中的任务T take(){// 此时只会有一个线程执行,所以不需要加锁T t = _task_queue.front();_task_queue.pop();return t;}~ThreadPool(){pthread_cond_destroy(&_cond);pthread_mutex_destroy(&_mutex);for(const auto& t : _threads)delete t;}
private:// 线程将来在此获取来自任务队列中的任务和执行任务static void* handlerTask(void* args){ThreadData<T>* td = static_cast