基于C++11实现的线程池,任务队列满和池中线程满时做了简单处理

在我们进行实际编程时,经常会碰到数量级大、耗时长的任务,尤其在网络服务中,可能有几万个用户同时访问你的服务器,不可能进来一个用户我们就开辟一个线程,这样做的后果是当用户量大的时候线程数量过多,线程间的调度效率就很低下了,反而会影响程序的效率。因此这时候,我们可以通过线程池,对一定数量的线程进行复用,提高应用的效率。线程池的结构是根据设计模式中的生产者消费者模式进行设计的,感兴趣的朋友可以百度学习一下。
这几天看了网上几篇的线程池实现代码,本篇文章主要参考以下博客内容:

https://www.jianshu.com/p/eec63026f8d0

上面这篇博客基于C++11新特性用100行代码实现了线程池,代码比较简洁易读,但是没有对任务队列的最大数量进行限制,以及在线程池中线程空闲线程为0时没有相应的处理。
本文主要根据参考博客实现以下功能:
1.管理一定数量的线程,当有任务commit进来的时候,唤醒池中的线程处理任务。
2.当线程池中空闲线程为0时,等待其他线程处理完毕,再进行线程唤醒
3.对任务队列最大数量进行限制,当任务队列满时,阻塞commit函数调用的线程

具体代码如下,代码中基本每一行都加了注释:

#include <vector>
#include <queue>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
#include <QDebug>
#include <iostream>
#include <omp.h>
class threadpool
{
private:
    using Task = std::function<void()>; //using 相当于typedef
    std::vector<std::thread> m_threads;
    std::queue<Task> m_tasks;  //任务队列
    std::mutex m_lock;
    std::condition_variable m_cvTask; //条件变量
    std::atomic<bool> m_stoped; //是否关闭提交 std::atomic<T> 对于对象的操作都是原子的不用加锁
    std::atomic<int>  m_idlThrNum; //空闲线程数量
    //std::atomic<bool> m_stoped;
    std::atomic<int>  m_taskNum;
    std::atomic<int>  m_maxTaskNum;  //任务队列的最大任务数量
public:
    inline threadpool(unsigned short size = 4,int maxTask = 500) :m_stoped(false)
    {
        m_idlThrNum = size < 1 ? 1 : size; //如果传入的构造线程数量为1以下,那默认为1
        m_maxTaskNum= maxTask< 1 ? 1 : maxTask;
        m_taskNum.store(0);
        for (size = 0; size < m_idlThrNum; ++size)
        {
            m_threads.emplace_back([this]{
                while(!this->m_stoped)//如果关闭为假,执行循环
                {
                    Task task;
                    {
                        std::unique_lock<std::mutex> lock(this->m_lock);
                        this->m_cvTask.wait(lock,[this]{
                            //当停止为真或者任务队列不为空时信号量触发,取消阻塞状态
                            return this->m_stoped.load() || !this->m_tasks.empty();
                        });
                        //当信号量触发前一直阻塞在这里
                        if(this->m_stoped && this->m_tasks.empty())
                            return;//当触发线程池停止时,任务队列也为空,就结束线程
                        task = std::move(this->m_tasks.front()); // 取一个 task
                        this->m_tasks.pop();
                    }
                    m_idlThrNum--; //std::atomic<int> 是原子的,因此这里不用加锁
                    task();
                    m_idlThrNum++;
                }
            });
        }
        //单独的线程用于通知线程池执行任务
        std::thread m_notifyThread([this]{
            while(!this->m_stoped.load())
            {
               if(m_idlThrNum.load()>0&&m_taskNum.load()>0){
                   //m_taskNum--;
                   m_taskNum.store(m_taskNum.load()-1);
                   m_cvTask.notify_one(); // 唤醒一个线程执行
                   printf("---------------notify_one------------- \n");
               }
               std::this_thread::yield();
            }
        });//通知线程
        m_notifyThread.detach();
    }

    inline ~threadpool()
    {
        m_stoped.store(true);
        m_cvTask.notify_all(); // 唤醒所有线程执行
        for (std::thread& thread : m_threads) {
            //thread.detach(); // 让线程“自生自灭”
            if(thread.joinable())
                thread.join(); // 等待任务结束, 前提:线程一定会执行完
        }
    }
    template<class F, class... Args>
    auto commit(F&& f, Args&&... args) ->std::future<decltype(f(args...))>
    {
        if (m_stoped.load()){
            throw std::runtime_error("commit on ThreadPool is stopped.");
        }
        using RetType = decltype(f(args...)); //获取函数f的返回值类型
        auto task = std::make_shared<std::packaged_task<RetType()> >(
                    std::bind(std::forward<F>(f), std::forward<Args>(args)...)
                    );

        //如果任务队列已达到最大容量,阻塞线程,等待历史任务处理
        while(m_taskNum.load()>m_maxTaskNum.load())
        {
            //std::this_thread::yield();
            std::this_thread::sleep_for(std::chrono::milliseconds(4));
        }
        std::future<RetType> future = task->get_future();
        {
            std::lock_guard<std::mutex> lock(m_lock);
            m_tasks.emplace(
                        [task]()
            {
                (*task)();
            }
            );
        }
       // qDebug()<<"commit one!current task num: "<<m_taskNum;
        m_taskNum++;
        //m_cvTask.notify_one(); // 唤醒一个线程执行
        return future;
    }

    //空闲线程数量
    int idlCount()
    {
        return m_idlThrNum;
    }
};

代码中包含了一些C++11的新特性:
1.使用匿名函数lambda表达式,让程序更加简洁,格式如下

//格式如下:
[捕获列表] (参数表){
			//代码段
}
//例如
auto fun =[](){
 std::cont<<"this is lambda!"<<std::endl;
}
fun();//即可调用函数

深入了解可自行百度。

2.std::atomic 模板类的对象操作都是原子的,因此本文我们对m_stoped、m_idlThrNum等多线程中共享的资源不需要进行加锁。

3.std::condition_variable信号量的使用,信号量调用wait()函数时,可以将线程阻塞在wait处,等待notify_one()或者notify_all()唤醒。

std::unique_lock<std::mutex> lock(this->m_lock);
this->m_cvTask.wait(lock,[this]{
//当停止为真或者任务队列不为空时信号量触发,取消阻塞状态
      return this->m_stoped.load() || !this->m_tasks.empty();
});
//
m_cvTask.notify_one();//唤醒一个线程
//在调用notify_one()后,如果this->m_stoped.load() || !this->m_tasks.empty()为真
//即可唤醒线程

4.还有一些std::future,std::bind以及可变模板参数的写法,想要深入了解可以自行百度。

参考博客中,对于线程池中空闲线程为0的情况没做处理,也就意味着当线程都忙的情况下,我们再提交任务不会有线程对任务进行处理,这样肯定不符合实际应用要求,因此这里我们通过单独开辟一个通知线程,进行线程唤醒工作,这里为什么单独开辟线程,一是不想再线程忙的情况下阻塞commit线程(也可在m_idlThrNum为0时,阻塞commit()线程,但是由于一般线程池中线程数量较少,需要提交的任务多的情况下阻塞线程时间会较长,体验感不好),具体实现如下:

 //单独的线程用于通知线程池执行任务
        std::thread m_notifyThread([this]{
            while(!this->m_stoped.load())
            {
               if(m_idlThrNum.load()>0&&m_taskNum.load()>0){
                   //m_taskNum--;
                   m_taskNum.store(m_taskNum.load()-1);
                   m_cvTask.notify_one(); // 唤醒一个线程执行
                   printf("---------------notify_one------------- \n");
               }
               std::this_thread::yield();
            }
        });//通知线程
        m_notifyThread.detach();

这里再循环中我们使用std::this_thread::yield(),这地方是有门道的,std::this_thread::yield():当前线程放弃执行,操作系统调度另一线程继续执行。即当前线程将未使用完的“CPU时间片”让给其他线程使用,等其他线程使用完后再与其他线程一起竞争"CPU"。因此这里使用std::this_thread::yield()会比std::this_thread::sleep_for(std::chrono::milliseconds(3));好一点。大家可以自行百度一下,如果我理解错了麻烦评论告诉我。

接下来编写代码对线程池进行测试:

void fun1(int slp){
    printf("  hello, fun1 !  %d\n" ,std::this_thread::get_id());
    std::this_thread::sleep_for(std::chrono::milliseconds(slp*1000));
    printf("sleep end\n");
}
struct gfun {
    int operator()(int n) {
        for(int i=0;i<5;i++){
            lock.lock();
            printf("%d  hello, gfun !  %d\n" ,n, std::this_thread::get_id() );
            lock.unlock();
            std::this_thread::sleep_for(std::chrono::milliseconds(2000));
            lock.lock();
            printf("%d  hello, gfun !  %d\n" ,n, std::this_thread::get_id() );
            lock.unlock();
        }

        return 42;
    }
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
    threadpool thrPool(4,5);
    std::cout << " =======  main thread ========= " << std::this_thread::get_id() << std::endl;
//    std::future<void> ff = thrPool.commit(fun1,10);
//    std::future<int> fg = thrPool.commit(gfun(),10);
//    //ff.get();
//    std::cout <<"get end"<< std::endl;//<<fg.get()

    for(int i=0;i<20;i++)
    {
        thrPool.commit(fun1,5);
    }
    printf("=======get end=======\n");
    return a.exec();

}

以上测试代码是在Qt的控制台程序中写的,因此会出现QCoreApplication ,可以忽略,只看关键代码,将关键代码拷贝到VS中也可运行。
运行结果如下:

 =======  main thread ========= 1
---------------notify_one-------------
---------------notify_one-------------
  hello, fun1 !  2
  hello, fun1 !  3
---------------notify_one-------------
  hello, fun1 !  4
---------------notify_one-------------
  hello, fun1 !  5
sleep end
---------------notify_one-------------
  hello, fun1 !  2
sleep end
---------------notify_one-------------
  hello, fun1 !  3
sleep end
---------------notify_one-------------
  hello, fun1 !  4
sleep end
---------------notify_one-------------
  hello, fun1 !  5
sleep end
---------------notify_one-------------
  hello, fun1 !  2
sleep end
  hello, fun1 !  3
sleep end
---------------notify_one-------------
  hello, fun1 !  4
sleep end
  hello, fun1 !  5
sleep end
---------------notify_one-------------
  hello, fun1 !  2
sleep end
  hello, fun1 !  3
sleep end
---------------notify_one-------------
  hello, fun1 !  4
sleep end
  hello, fun1 !  5
sleep end
---------------notify_one-------------
  hello, fun1 !  2
sleep end
---------------notify_one-------------
  hello, fun1 !  3
=======get end=======
sleep end
sleep end
  hello, fun1 !  4
---------------notify_one-------------
  hello, fun1 !  5
sleep end
---------------notify_one-------------
---------------notify_one-------------
---------------notify_one-------------
sleep end
---------------notify_one-------------
sleep end
---------------notify_one-------------
sleep end

本篇博客是博主学习线程池后的记录,水平有限,如果上文有出现理解错误的地方,大家可以评论告诉我,感谢。

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用C++11实现线程池的示例代码: ```cpp #include <iostream> #include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <functional> class ThreadPool { public: ThreadPool(size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { threads_.emplace_back([this] { while (true) { std::unique_lock<std::mutex> lock(mutex_); condition_.wait(lock, [this] { return !tasks_.empty() || stop_; }); if (stop_ && tasks_.empty()) return; auto task = std::move(tasks_.front()); tasks_.pop(); lock.unlock(); task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(mutex_); stop_ = true; } condition_.notify_all(); for (auto& thread : threads_) thread.join(); } template<class F> void Enqueue(F&& task) { { std::unique_lock<std::mutex> lock(mutex_); tasks_.emplace(std::forward<F>(task)); } condition_.notify_one(); } private: std::vector<std::thread> threads_; std::queue<std::function<void()>> tasks_; std::mutex mutex_; std::condition_variable condition_; bool stop_ = false; }; int main() { ThreadPool pool(4); for (int i = 0; i < 8; ++i) { pool.Enqueue([i] { std::cout << "Task " << i << " is running" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Task " << i << " is done" << std::endl; }); } return 0; } ``` 此示例实现了一个线程池,其包含一个任务队列和一组工作线程。构造函数创建指定数量的工作线程,并启动它们以侦听任务队列。Enqueue()方法用于将新任务添加到队列。每个工作线程将从队列获取任务并执行它。当线程池被销毁时,所有工作线程都将被停止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值