• 半同步半异步线程池


    1、半同步半异步线程池

      在处理大量并发任务的时候,如果一个请求一个线程来处理任务,大量的线程创建将会消耗过多的资源,还增加了线程上下文切换的开销,而通过线程池技术可以很好的解决这个问题。

      线程池技术通过在系统中预先创建一些线程,当任务请求来到的时候,从线程池中分配一个预先创建的线程来处理任务,在任务完成之后,线程不会销毁,还可以重用,等待下次任务的到来。这样就避免了大量线程的创建和销毁,从而节省系统资源,对于多核处理器,线程可以分配到多个cpu,提高并发效率。每个线程处理任务,独立阻塞,防止主线程被阻塞,导致其他请求得不到响应。

      线程池分为半同步半异步线程池和领导者追随者线程池,半同步半异步线程池在实现上更加的简单,使用方便,也是最常用的线程池。

      

      第一层为同步服务层,处理来自上层的任务请求,上层的任务请求是并发的,并不会马上处理,而是将这些任务放到一个同步队列中,等待处理。

      第二层为同步队列,来自上层的任务都会加到队列中等待处理。

      第三层是异步服务层,这一层中有多个线程同事处理排队任务中的任务,异步服务层从同步队列中取出任务并行的处理任务。

      这种三层的结构可以最大程度的处理上层的并发请求,对于上层来说,只需要往队列中添加任务,不关心谁处理任务,主线程也不会阻塞,对于任务的具体处理,通过异步服务层的多线程异步并行来完成。

    2、线程池

      中间层是一个同步队列,允许多个线程同时添加或者取出任务,并且要保证操作过程是线程安全的。线程池有两个活动,一个是往同步队列中添加任务,另一个是从同步队列中取出任务。

      

      启动线程池会启动一定数量的线程,这些线程属于异步层,主要用来并行处理队列中任务,如果队列中的任务为空,则等待任务的到来,如果有任务,则从等待的线程中唤醒一定数量的线程来执行任务。

      需要注意的是,队列中一定要限制任务的数量,避免任务过多导致内存暴涨。

    3、同步队列

      同步队列保存任务,为同步层提供添加新任务的接口,为异步层提供取任务的接口。同步队列中的锁用来线程同步,条件变量用来实现线程通信。

    #include <list>
    #include <mutex>
    #include <thread>
    #include <condition_variable>
    #include <iostream>
    
    template<typename T>
    class SynQueue
    {
    public:
        SynQueue(int maxsize) : m_maxSize(maxsize), m_needStop(false) { }
    
        //添加事件,左值拷贝和右值移动
        void Put(const T&x)
        {
            //调用private内部接口Add
            Add(x);
        }
    
        void Put(T &&x)
        {
            Add(x);
        }
    
        //从队列中取事件,取所有事件
        void Take(std::list<T> &list)
        {
            //有wait方法必须用unique_lock
            //unique_lock有定时等待等功能,lock_guard就仅仅是RAII手法的互斥锁
            //但unique_lock的性能稍低于lock_guard
            std::unique_lock<std::mutex> locker(m_mutex);
            //满足条件则唤醒,不满足阻塞
            m_notEmpty.wait(locker, [this] { return m_needStop || NotEmpty(); });
    
            if (m_needStop)
            {
                return;
            }
    
            std::cout << "取出所有任务..." << std::endl;
            list = std::move(m_queue);
            //唤醒其他阻塞在互斥锁的线程
            m_notFull.notify_one();
        }
    
        //取一个事件
        void Take(T &t)
        {
            std::unique_lock<std::mutex> locker(m_mutex);
            m_notEmpty.wait(locker, [this]    { return m_needStop || NotEmpty(); });
            if (m_needStop)
            {
                return;
            }
            
            std::cout << "取出一个任务..." << std::endl;
            t = m_queue.front();
            m_queue.pop_front();
            m_notFull.notify_one();
        }
    
        //停止所有线程在同步队列中的读取
        void Stop()
        {
            {
                std::lock_guard<std::mutex> locker(m_mutex);
                m_needStop = true;
            }
    
            std::cout << "停止任务执行..." << std::endl;
            m_notFull.notify_all();
            m_notEmpty.notify_all();
        }
    
        //队列为空
        bool Empty()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.empty();
        }
    
        //队列为满
        bool Full()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.size() == m_maxSize;
        }
    
        //队列大小
        size_t Size()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.size();
        }
    
    private:
    
        //往队列里添加事件,事件是范型的,c++11我们可以把函数通过std::function封装为对象。
        template<typename F>
        void Add(F &&x)
        {
            std::unique_lock<std::mutex> locker(m_mutex);
            m_notFull.wait(locker, [this] { return m_needStop || NotFull(); });
            if (m_needStop)
            {
                return;
            }
            
            std::cout << "添加一个任务..." << std::endl;
            m_queue.push_back(std::forward<F>(x));
            m_notEmpty.notify_one();
        }
    
        //队列未满
        bool NotFull() const
        {
            bool full = m_queue.size() >= m_maxSize;
            if (full)
            {
                std::cout << "任务池满,等待取出任务..." << "线程ID:" << std::this_thread::get_id() << std::endl;
            }
    
            return !full;
        }
    
        //队列不为空
        bool NotEmpty() const
        {
            bool empty = m_queue.empty();
            if (empty)
            {
                std::cout << "任务池为空,等待添加任务..." << "线程ID:" << std::this_thread::get_id() << std::endl;
            }
    
            return !empty;
        }
    
    private:
        std::mutex m_mutex;                                  //互斥锁
        std::list<T> m_queue;                                //队列,存放任务
        std::condition_variable m_notEmpty;            //队列不为空的条件变量
        std::condition_variable m_notFull;              //队列不为满的条件变量
        size_t m_maxSize;                                   //任务队列最大长度 
        bool m_needStop;                                    //终止标识
    };

    4、线程池

      一个完整的线程池包括三种:同步服务层、排队层和异步服务层,其实也是一种消费者生产者模式,同步层是生产者,不断将新任务丢到队列中,因此线程池需要提供一个添加任务的接口;消费者是异步层,具体是从线程池中预先创建的线程去处理队列中的任务。线程池还提供一个停止的接口,在需要的时候,停止线程池的运行。

    #include "SyncQueue.hpp"
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <memory>
    
    const int MaxTaskCount = 10;
    
    class ThreadPool
    {
    public:
    
        //规定任务类型为void(),我们可以通过c++11 不定参数模板来实现一个可接受任何函数的范型函数模板,这样就是一个可以接受任何任务的任务队列了。
        using Task = std::function<void()>;
    
        //hardware_concurrency检测硬件性能,给出默认线程数
        ThreadPool(int numThreads = std::thread::hardware_concurrency()) : m_queue(MaxTaskCount)
        {
            //初始化线程,并通过shared_ptr来管理
            Start(numThreads);
        }
    
        //销毁线程池
        ~ThreadPool(void)
        {
            Stop();
        }
    
        //终止所有线程,call_once保证函数只调用一次
        void Stop()
        {
            std::call_once(m_flag, [this] { StopThreadGroup(); });
        }
    
        //添加任务,普通版本和右值引用版本
        void AddTask(const Task& task)
        {
            m_queue.Put(task);
        }
    
        void AddTask(Task && task)
        {
            m_queue.Put(std::forward<Task>(task));
        }
    
    private:
    
        //停止线程池
        void StopThreadGroup()
        {
            m_queue.Stop();
            m_running = false;
            for (auto thread : m_threadgroup)
            {
                if (thread)
                    thread->join();
            }
    
            m_threadgroup.clear();
        }
    
        void Start(int numThreads)
        {
            m_running = true;
            std::cout << "初始化线程池..." << std::endl;
            for (int i = 0; i < numThreads; ++i)
            {
                //智能指针管理,并给出构建线程的参数,线程调用函数和参数
                m_threadgroup.push_back(std::make_shared<std::thread>(&ThreadPool::RunInThread, this));
            }
        }
    
        //一次取出队列中全部事件
        void RunInThread_list()
        {
            std::cout << "取出所有任务并执行..." << std::endl;
            while (m_running)
            {
                std::list<Task> list;
                m_queue.Take(list);
                for (auto &task : list)
                {
                    if (!m_running)
                    {
                        return;
                    }
    
                    task();
                }
            }
        }
    
        //一次只取一个事件
        void RunInThread()
        {
            while (m_running)
            {
                Task task;
                if (!m_running)
                    return;
    
                m_queue.Take(task);
    
                //执行任务
                task();
            }
        }
    
    private:
        //线程池
        std::list<std::shared_ptr<std::thread>> m_threadgroup;
        //任务队列
        SynQueue<Task>m_queue;
        //原子布尔值
        std::atomic_bool m_running;
        //辅助变量->call_once
        std::once_flag m_flag;
    };

      线程池中,有三个成员变量,一个是线程组,线程组中的线程是预先创建的,具体创建多少个,由外部决定,一般是CPU核数的线程达到最优的效率,线程组循环从同步队列中取出任务并执行,如果任务为空,线程组等待。另一个成员变量是同步队列,不仅用来做线程同步,还用来限制同步队列的上限。第三个成员变量是用来停止线程池的,为了保证线程安全,用了原子变量atomic_bool。

    5、线程池的使用

      通过简单的例子来使用半同步半异步线程池。线程池创建两个线程,通过外部线程不断地想线程池添加新任务,线程池内部的线程将会并行的处理同步队列中的任务。

    #include <thread>    
    #include <iostream> 
    
    #include "ThreadPool.hpp"
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        ThreadPool pool;
    
        //pool.Start(2);
        std::thread thd1([&pool]
        {
            for (int i = 0; i < 10; i++)
            {
                auto thdId = this_thread::get_id();
                cout << "线程 1 添加任务..." << endl;
                pool.AddTask([thdId] {cout << "线程1任务,线程ID:" << thdId << "	执行线程:" << this_thread::get_id() << endl; });
            }
        });
    
        std::thread thd2([&pool]
        {
            for (int i = 0; i < 10; i++)
            {
                auto thdId = this_thread::get_id();
                cout << "线程 2 添加任务..." << endl;
                pool.AddTask([thdId]    { cout << "线程2任务,线程ID:" << thdId << "	执行线程:" << this_thread::get_id() << endl;  });
            }
        });
    
        thd1.join();
        thd2.join();
    
        //等待线程池执行30s
        cout << "主线程休眠等待..." << endl;
        this_thread::sleep_for(std::chrono::seconds(30));
        pool.Stop();
    
        return EXIT_SUCCESS;
    }
  • 相关阅读:
    (24)ajax上传json格式的数据
    (23)ajax实现上传文件的功能
    (22)Ajax的基本使用(实现登录功能和局部刷新以及防止跨站请求伪造攻击)
    (21)模型层 -ORM之msql 聚合查询,F和Q(与、或、非查询)、分组查询
    (20)模型层 -ORM之msql 基于双下划线的跨表查询(一对一,一对多,多对多)
    Python init.py
    python 模块积累-----subprocess
    linux修改root密码
    Django urls.py报错: raise TypeError('view must be a callable or a list/tuple in the case of include()
    Python------uuid
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7967001.html
Copyright © 2020-2023  润新知