线程池

本文深入探讨线程池的工作原理,包括其如何避免频繁创建和销毁线程带来的开销,提升系统性能。同时,提供了线程池的C++实现示例,详细解释了线程池的创建、任务分配及线程退出机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池:
    一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

 


线程池的应用场景
1. 需要大量的线程来完成任务,且完成任务的时间比较短。
2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

 


线程池示例
1. 创建固定数量线程池,循环从任务队列中获取任务对象,
2. 获取到任务对象后,执行任务对象中的任务接口
 

 

1.线程池 = 一个线程安全队列 + 一大堆线程

2.使用:

        2.1.线程池提供一个push接口,用来支持请求入队操作

        2.2.线程池当中的线程从队列当中获取数据,进行处理

3.线程池当中的线程都是等价的,逻辑上可以认为是消费线程。所有的线程调用的都是一个入口函数

4.线程安全队列当中的元素 = 待处理的数据 + 处理的函数

模拟实现:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <queue>

//线程池 = 线程安全队列 + 一大堆的线程
//线程安全队列
//    元素 = 数据 + 处理数据的函数地址

#define THREADCOUNT 4

typedef void* (*Handler_t)(int);
class ThreadTask
{
    public:
        ThreadTask(int data, Handler_t handler)
        {
            data_ = data;
            handler_ = handler;
        }

        ~ThreadTask()
        {
            //
        }

        //使用函数,处理数据
        void Run()
        {
            handler_(data_);
        }
    private:
        int data_;
        Handler_t handler_; //返回值为void* 参数为int
};

class ThreadPool
{
    public:
        ThreadPool()
        {
            capacity_ = 10;

            thread_capacity_ = THREADCOUNT;

            pthread_mutex_init(&lock_, NULL);
            pthread_cond_init(&cond_, NULL);

            //创建线程
            bool is_create = ThreadCreate();
            if(!is_create)
            {
                printf("Threadpool Create thread failed\n");
                exit(1);
            }

            IsExit = false;

            cur_thread_count_ = THREADCOUNT;
        }

        ~ThreadPool()
        {

        }

        bool Push(ThreadTask* tt)
        {
            pthread_mutex_lock(&lock_);
            if(IsExit)
            {
                pthread_mutex_unlock(&lock_);
                return false;
            }
            que_.push(tt);
            pthread_mutex_unlock(&lock_);

            //当插入数据之后 通知线程池当中的线程(消费者)
            pthread_cond_signal(&cond_);
            return true;
        }

        bool Pop(ThreadTask** tt)
        {
            *tt = que_.front();
            que_.pop();
            return true;
        }

        void ThreadJoin()
        {
            for(int i = 0; i < THREADCOUNT; i++)
            {
                pthread_join(tid_[i], NULL);
            }
        }

        // 如果直接退出线程,则有可能队列当中当中还有数据没有处理完毕;
        // 我们不应该去调用这样的接口来结束我们的线程
        //void ThreadExit()
        //{
            //for(int i = 0; i < THREADCOUNT; i++)
            //{
                //pthread_cancel(tid_[i]);
            //}
        //}

        void ThreadPoolClear()
        {
            //标志位
            pthread_mutex_lock(&lock_);
            IsExit = true;
            pthread_mutex_unlock(&lock_);

            if(cur_thread_count_ > 0)
            {
                pthread_cond_broadcast(&cond_);
            }
        }

    private:
        static void* ThreadStart(void* arg)
        {
            ThreadPool* tp = (ThreadPool*)arg;
            while(1)
            {
                //从队列当中获取数据,进行消费 对于不同的线程而言,在获取数据的时候,是互斥的
                pthread_mutex_lock(&tp->lock_);
                ThreadTask* tt;
                while(tp->que_.empty())
                {
                    //可以进行结束
                    if(tp->IsExit)
                    {
                        //退出
                        tp->cur_thread_count_--;
                        pthread_mutex_unlock(&tp->lock_);
                        pthread_exit(NULL);
                    }
                    //调用条件变量 等待接口
                    pthread_cond_wait(&tp->cond_, &tp->lock_);
                }
                tp->Pop(&tt);
                pthread_mutex_unlock(&tp->lock_);

                //调用队列当中元素提供的函数,去处理数据 对于线程走到该位置的时候,就可以并行的处理了
                tt->Run();
                //防止内存泄漏
                delete tt;
            }
        }

        bool ThreadCreate()
        {
            for(int i = 0; i < THREADCOUNT; i++)
            {
                int ret = pthread_create(&tid_[i], NULL, ThreadStart, (void*)this);
                if(ret != 0)
                {
                    perror("pthread_create");
                    return false;
                }
            }
            return true;
        }
    private:
        std::queue<ThreadTask*> que_;
        size_t capacity_;

        //互斥
        pthread_mutex_t lock_;
        //同步 消费线程的条件变量,但是并没有生产线程的条件变量
        //由于客户端的请求行为我们是无法控制的。所以就不需要通知生产者来进行生产,当生产线程有了数据,就直接往线程池当中抛入就可以了,在通知消费线程来进行消费
        pthread_cond_t cond_;

        //线程池当中的初始化的时候线程数量
        size_t thread_capacity_;

        //标识具体还有多少线程数量
        size_t cur_thread_count_;

        //保存线程池当中的线程的线程标识符
        pthread_t tid_[THREADCOUNT];

        //标志是否可以退出
        bool IsExit;
};

void* DealData(int data)
{
    printf("consume data is %d\n", data);
    return NULL;
}

int main()
{
    ThreadPool* tp = new ThreadPool();
    //在这个代码当中main函数的线程,就充当生产线程,往线程池的线程安全队列当中push数据
    for(int i = 1; i <= 50; i++)
    {
        ThreadTask* tt = new ThreadTask(i, DealData);
        tp->Push(tt);
    }

    //等待线程池当中线程退出
    sleep(15);
    tp->ThreadPoolClear();
    tp->ThreadJoin();
    delete tp;
    return 0;
}

线程池线程退出

如果直接退出,可能线程池当中安全队列还有数据没有处理完成

有如下几种情况:

    1.加互斥锁--》加锁--》判断是否为空

    2.调用pthread_wait--》阻塞在pthread_cond_wait接口中

    3.在队列中获取数据--》获取成功--》处理数据--》继续进入循环--》加锁--》判断.....

    4.正在处理队列中数据--》处理数据--》继续进入循环--》加锁--》判断.....

只有在线程判断了当前队列中没有数据才可以退出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值