C++11缓存式线程池


一、概述

        动态调整线程数量:与固定式线程池不同,缓存式线程池的线程数量是动态调整的。当有新任务提交时,如果线程池中有空闲的线程,则会立即使用空闲线程执行任务;如果线程池中没有空闲线程,则会创建一个新的线程来执行任务。当线程空闲一段时间后,超过线程最大空闲时间(默认为60s),线程将会被回收和销毁。

二、同步队列的设计

        对于缓存式线程池同步队列的设计,首先会在类中设置容器来存放任务(list),另外使用mutex来实现生产者和消费者的互斥关系,以及两个条件变量(生产者条件变量m_notFull,消费者条件变量m_notEmpty)来唤醒两者。需要设计m_waitTime来限制任务队列满等待时间,以及m_maxSize来限制任务队列最大任务数量,防止任务数过多撑满内存。最后,需要一个布尔类型变量m_needStop来设置同步队列启动与否。

        在设置生产者和消费者模型时(Add和Take),需要设置条件变量的等待时间,当到达m_waitTime,说明出现超时,此时便可以直接退出,防止线程长期占用CPU;在停止同步队列时,即Stop(),首先需要进行加锁,不能让生产者和消费者此时再去获取锁,设置关闭同步队列标志m_needStop,唤醒生产者和消费者,此时生产者和消费者就会退出。

三、缓存式线程池的设计

        对于缓存式线程池的设计,需要设置变量KeepAliveTime线程最大存活时间,当线程获取不到任务时或者同步队列为空,线程的最大存活时间超过KeepAliveTime时,就会释放该空闲线程。

        注意可调用对象包装器std::function和打包器std::bind的使用,通常使用这两种特性来作为生产者添加任务。using Task=std::function<void(void)>

        在类中设置unordered_map<std::thread::id,std::shared_ptr<std::thread>> m_threadgroup来作为线程组管理线程。其中,m_coreThreadSize->核心线程数量,也作为缓存式线程池线程数下限,m_maxThreadSize->上限,最大线程数量,对于最大线程数量可以通过hardware_concurrency()来设置系统支持并发数量线程近似值.另外,还会有m_idleThreadSize->空闲线程数量,m_curThreadSize->当前线程池中线程总数量。

        SyncQueue<Task> m_queue,在类中设置同步队列,并对同步队列进行初始化。

        使用原子变量atomic_bool m_running。

以下提供线程池中主要函数功能:

void Start(int numthreads)//启动线程池
{
    m_running=true;
    m_curThreadSize=numthreads;//当前线程数量
    for(int i=0;i<numthreads;++i)
    {
        auto tha=std::make_shared<std::thread>(std::thread(&CachedThreadPool::RunInThread,this));
        std::thread::id tid=tha->get_id();
        m_threadgroup.emplace(tid,std::move(tha));
        m_idleThreadSize++;//空闲线程数++
    }
}

 

void RunInThread()//线程回调函数,启动线程,从缓冲区获取任务
{
    auto tid=std::this_thread::get_id();
    auto startTime=std::chrono::high_resolution_clock().now();
    while(m_running)
    {
        Task task;
        if(m_queue.size()==0&&m_queue.notTask())
        {
            auto now=std::chrono::high_resolution_clock().now();
            auto intervalTime=std::chrono::duration_cast<std::chrono::seconds>(now-startTime);
            std::lock_guard<std::mutex> lock(m_mutex);
                                if(intervalTime.count()>=KeepAliveTime&&m_curThreadSize>m_coreThreadSize)
            {
                m_threadgroup.find(tid)->second->deatch();//从主线程中分离出来该线程
                m_threadgroup.erase(tid);
                m_curThreadSize--;
                m_idleThreadSize--;
                return;
            }

        }
        if(!m_queue.Take(task)&&m_running)
        {
            m_idleThreadSize--;
            task();
            m_idleThreadSize++;
        }
    }
}

 

void StopThreadGroup()
{
    m_queue.Stop();
    m_running=false;
    for(auto& thread:m_threadgroup)
    {
        thread.second->join();
    }
    m_threadgroup.clear();
}
template<class Func,class...Args>
auto submit(Func&& func,Args&&... args)->std::future<decltype(func(args...))>
{
    using RetType=decltype(func(args...));
    auto task=std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<Func>(func),std::forward<Args>(args)...));
    std::future<RetType> result=task->get_future();
    if(m_queue.Put([task]() {(*task)();})!=0)
    {
        cout<<"调用者运行策略"<<endl;
        (*task)();
    }
    if(m_idleThreadSize<=0&&m_curThreadSize<m_maxThreadSize)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        auto tha=std::make_shared<std::thread>(std::thread(&CachedThreadPool::RunInThread,this));
        std::thread::id tid=tha->get_id();
        m_threadgroup.emplace(tid,std::move(tha));
        m_idleThreadSize++;
        m_curThreadSize++;
    }
    return result;
}

四、固定式线程池和缓存式线程池比较

        固定式线程池与缓存式线程池差不多,也是能使用就使用,但不能随时创建新的线程。可指定n Threads,固定数量。固定式线程池多数针对一些很稳定很固定的正规并发线程,多用于服务器。定长线程池,适用于执行负载重,cpu使用频率高的任务,这个主要是为了防止太多线程进行大量的线程切换。

        缓存式线程池先查看线程池中有无空闲线程,如果有就使用,如果没有,就创建一个新的线程加入池子中。线程池线程数目存在下限和上限。空闲线程最大存活60s,放入缓存式线程池的线程不必担心其结束,超过timeout时间不活动模糊,会被终止。适用于执行大量并发短期异步的任务;注意,任务量的负载要轻。

五、适用场景

1、大量短期任务:缓存式线程池适合处理大量的短期任务,当任务到来时尽可能地创建新线程来执行任务,如果有空闲的线程可用则会重复利用现有线程,而不会让线程闲置。这样可以避免因为创建线程频繁和销毁线程带来的额外开销。

2、任务响应快速:缓存式线程池适合处理需要快速相应的任务,因为它可以根据任务的到来快速创建和启动新线程来执行新任务,从而减少任务等待时间。

3、不需要限制线程数量:只要内存空间够,可以根据任务地到来动态创建新的线程。

4、短期性任务的高并发性:缓存式线程池可以根据需要动态地创建线程,所以适合处理需要高并发性的短期性任务。当任务处理完毕后,线程池会保持一定的空闲线程用于下一批任务的到来。

### 关于缓存池的概念 缓存池是一种用于存储临时数据的数据结构,旨在提高应用程序性能并减少重复计算或网络请求的时间开销。通过将频繁访问的数据保存在快速可访问的位置(如内存),可以显著加快读取速度。 对于 Android 应用程序而言,存在两种主要类型的缓存机制:基于内存的缓存和基于磁盘的缓存[^1]。这两种形都遵循一定的管理算法以优化资源利用效率;其中最常用的算法之一即为 LRU (Least Recently Used),它会优先移除那些最近最少被使用的条目来腾出空间给新的或者更常访问的内容[^2]。 ### 缓存池的实现方 #### 内存缓存 为了有效地管理和控制应用中的内存占用情况,在 Java 和 Kotlin 开发环境下推荐使用 `LruCache` 类作为内存级别的缓存解决方案。该类实现了简单的键值对映射关系,并内置了LRU清除逻辑当达到预设容量上限时自动淘汰旧项目。 ```java // 创建一个大小为 10 MB 的 LruCache 实例 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; // 使用最大可用堆栈量的八分之一 LruCache<String, Bitmap> bitmapCache = new LruCache<>(cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount() / 1024; } }; ``` #### 磁盘缓存 除了内存外,还可以考虑使用文件系统上的特定目录来进行持久化存储。这通常适用于较大尺寸的对象比如图片、视频片段等不适合长期驻留在 RAM 中的情况。第三方库如 Glide 或 Picasso 已经很好地集成了此类功能以便开发者轻松集成到自己的项目当中。 ### 缓存池的应用场景 - **图像加载**:在网络应用中经常遇到需要展示大量远程获取的小图标或大图的情形下,合理设置好各级别的缓存可以帮助节省带宽消耗以及提升用户体验流畅度。 - **API 请求结果**:某些 RESTful API 接口返回的结果可能不会频繁变动,因此可以在本地建立相应的缓存副本从而降低服务器压力的同时也提高了响应时间。 - **页面渲染加速**:Web 浏览器内部也会维护一套复杂的 DOM 结构快照体系用来支持前进/后退操作而无需重新下载整个网页内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值