c++11实现同步、异步线程池

先看头文件,构造函数的min和max是传入的最小和最大线程数量,thread::hardware_concurrency用于查询系统中可以并行执行的线程数量,也就是 CPU 核心数。有个添加任务的函数: void addTask(function<void(void)> task);  这个我们应该都知道,通过包装器打包成一个可调用对象task,返回值是和参数都是void。m_exitThread是当管理者线程判定需要销毁一些工作线程的时候,对应的要销毁的线程数量。

另外通常我们只需要一个线程池实例,所以可以把线程池的拷贝和赋值构造函数delete,把构造函数设置为私有,通过公共的getInstance接口可以获取唯一的线程池实例。

#pragma once

#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
using namespace std;

class ThreadPool {
public:
  ~ThreadPool();
  //添加任务
  void addTask(function<void(void)> task);
  static ThreadPool &getInstance() {
    static ThreadPool instance;
    return instance;
  }

private:
  ThreadPool(int min = 2, int max = thread::hardware_concurrency());
  ThreadPool(const ThreadPool &) = delete;
  ThreadPool &operator=(const ThreadPool &) = delete;

private:
  void manager(void);
  void worker(void);

private:
  thread *m_manager; //管理者线程

  vector<thread::id> m_ids;          //储存已经退出任务函数的线程
  map<thread::id, thread> m_workers; //储存线程对象
  atomic<int> m_minThread;           //最小线程数量
  atomic<int> m_maxThread;           //最大线程数量
  atomic<int> m_curThread;           //当前线程数量

  atomic<int> m_idleThread; //空闲线程数量
  atomic<int> m_exitThread;
  atomic<bool> m_stop;                 //线程池开关
  queue<function<void(void)>> m_tasks; //任务队列
  mutex m_queueMutex;
  mutex m_idsMutex;
  condition_variable m_condition;
};

再看源文件,在构造函数中创建工作线程以及管理者线程。当添加任务后,如果用的是notify_all来唤醒阻塞的工作线程取任务,会引发虚假唤醒问题,关于虚假唤醒,我前面的博客已经解决过了。

#include "ThreadPool.h"
#include <iostream>
ThreadPool::ThreadPool(int min, int max)
    : m_minThread(min), m_maxThread(max), m_curThread(min), m_idleThread(min),
      m_exitThread(0), m_stop(false) {
  //创建管理者线程
  m_manager = new thread(&ThreadPool::manager, this);
  //创建工作线程
  cout << "max:" << max << endl;
  for (int i = 0; i < min; i++) {
    thread t(&ThreadPool::worker, this);
    //注意,线程对象不允许拷贝:thread(const thread&)=delete;
    m_workers.insert(make_pair(t.get_id(), move(t)));
  }
}
ThreadPool::~ThreadPool() {
  m_stop = true;
  m_condition.notify_all();
  for (auto &it : m_workers) {
    thread &t = it.second;
    if (t.joinable()) {
      cout << "析构函数执行!线程" << t.get_id() << "将要退出!" << endl;
      t.join();
    }
  }
  if (m_manager->joinable()) {
    m_manager->join();
  }
  delete m_manager;
}

void ThreadPool::manager(void) {
  while (!m_stop.load()) {
    //每隔三秒检测一次子线程数量
    this_thread::sleep_for(chrono::seconds(3));
    int idel = m_idleThread.load();
    int cur = m_curThread.load();
    cout << "当前线程数量:" << cur << "  空闲线程数量:" << idel << endl;
    //如果空闲线程数量大于总线程数量一半,销毁线程
    if (idel > cur / 2 && cur > m_minThread) {
      cout << "将要销毁线程" << endl;
      //一次销毁两个线程
      m_exitThread.store(2);
      m_condition.notify_all();
      // this_thread::sleep_for(chrono::seconds(1));
      lock_guard<mutex> locker(m_idsMutex);
      for (auto id : m_ids) {
        auto it = m_workers.find(id);
        if (it != m_workers.end()) {
          cout << "线程" << (*it).first << "被管理者线程销毁了" << endl;
          (*it).second.join();
          //因为任务函数已经退出,所以Join会立即返回
          // join等待子线程退出并回收资源,这个线程后续不可使用
          // detach工作完成之后会自动释放资源,线程后续依然可以使用
          m_workers.erase(it);
        }
      }
      m_ids.clear();
    } else if (idel == 0 && cur < m_maxThread) {
      thread t(&ThreadPool::worker, this);
      //注意,线程对象不允许拷贝:thread(const thread&)=delete;
      m_workers.insert(make_pair(t.get_id(), move(t)));
      m_curThread++;
      m_idleThread++;
    }
  }
}

void ThreadPool::addTask(function<void(void)> task) {
  {
    lock_guard<mutex> locker(m_queueMutex);
    m_tasks.emplace(task);
  }
  m_condition.notify_one();
}

void ThreadPool::worker(void) {
  while (!m_stop.load()) {
    function<void(void)> task = nullptr;

    {
      unique_lock<mutex> locker(m_queueMutex);
      while (m_tasks.empty() && !m_stop.load()) {
        m_condition.wait(locker);
        if (m_exitThread > 0) {
          m_exitThread--;
          m_curThread--;
          m_idleThread--;
          cout << "线程" << this_thread::get_id() << "的工作函数先退出!"
               << endl;
          lock_guard<mutex> locker(m_idsMutex);
          m_ids.push_back(this_thread::get_id());
          //在管理者线程释放被销毁的线程对象
          return;
        }
      }
      // m_condition.wait(locker, [&] { return !m_tasks.empty(); });
      if (!m_tasks.empty()) {

        cout << "取出了一个任务" << endl;
        task = move(m_tasks.front());
        m_tasks.pop();
      }
    }
    if (task) {
      m_idleThread--;
      task();
      m_idleThread++;
    }
  }
}

void calc(int x, int y) {
  int z = x + y;
  cout << "z=" << z << endl;
  cout << endl << endl << endl;
  this_thread::sleep_for(chrono::seconds(2));
}

int main() {
  ThreadPool &pool = ThreadPool::getInstance();
  for (int i = 0; i < 10; i++) {
    auto obj = bind(calc, i, i * 2);
    pool.addTask(obj);
  }
  getchar(); //阻塞主线程
  return 0;
}

关于worker函数和manager函数的实现,代码注释已经解释的很清楚了,我就不多说了,最后测试的时候发现,管理者线程销毁线程的时候,结果并非预期。关于管理者销毁线程的机制我们知道,是设置m_exitThread,然后唤醒所有工作线程,当某两个工作线程率先唤醒,并且判定自己将退出的时候,把自身id记录到了vector容器,然后return退出,然后manager通过遍历这个容器获取要销毁的线程id,调用join进行资源回收,并打印 cout << "线程" << (*it).first << "被管理者线程销毁了" << endl;但结果不是这样:

可以看到,管理者线程没有销毁vector容器中的线程资源,而是在最后的析构函数销毁的,为什么会这样?其实当管理者线程调用notify_all之后,就继续往下执行,可能还没来得及等到worker线程把自身id放入vector,就已经遍历结束了。因此我们可以加一句:

把中间注释的那行代码放开,就解决了这个问题:

现在,同步线程池就完成了,接下来实现异步线程池,需要用c++11提供的一些异步的类,我也在之前的博客介绍过它们了。

首先改一下原来的calc任务函数:

(通过promise调用set_value拿到结果,然后调用get_future把结果传递给future,然后future调用get来获取结果)

packaged_task 通过get_future与future对象关联,然后线程完成后可以通过future类获取结果)

因此promise和packaged_task 我们二选一就可以,在这里我选择packaged_task 。

因为我们改了任务函数,因此,我们还需要把void addTask(function<void(void)> task);
函数改一下,返回值就是future对象。

  template <typename F, typename... Args> // F是一个可调用对象
  auto addTask(F &&f, Args &&...args)
      -> future<typename result_of<F(Args...)>::type>

  {
    using returnType = typename result_of<F(Args...)>::type;
    //创建一个packaged_task对象
    packaged_task<returnType()> pkg(
        bind(forward<F>(f), forward<Args>(args)...));
    //得到future
    future<returnType> res = pkg.get_future();
    //把任务函数添加到队列
    m_queueMutex.lock();
    m_tasks.emplace([pkg = move(pkg)]() mutable { pkg(); });
    m_queueMutex.unlock();
    m_condition.notify_one();
    return res;
  }

这代码一看有点复杂,其实并不复杂,都是我之前博客讲过的万能引用和完美转发。

F是一个可调用对象,Args是可变参数,addTask的参数是万能引用,返回值是->指向的东西,也就是一个future对象,future内部类型是我们用result_of推导出来的结果,也就是任务函数的结果的类型,把它简化命名为returnType,然后用包装器把一个返回值为returnType,参数为Args的函数也就是任务函数包装成pkg,m_tasks.emplace([pkg = move(pkg)]() mutable { pkg(); }); 代码并不会立即执行pkg任务函数,而是将一个可调用对象(即 lambda)放入任务队列中。) 然后返回这个future对象,以便后续通过get获取函数执行结果。

再看一个细节,为什么不用&捕获pkg?因为如果 pkg 在 lambda 执行时已经被移动或者线程池已经被销毁,这样的引用可能会导致未定义行为。当然也可以用智能指针封装一下,然后捕获这个智能指针也没问题。

已经正确地使用了 std::move 来移动 pkg,但是编译还是报错了,因为 std::function 期望其目标类型是可以被拷贝构造的,而 std::packaged_task 不是。最终还是只能依靠指针上场:
 

  template <typename F, typename... Args> // F是一个可调用对象
  auto addTask(F &&f, Args &&...args)
      -> future<typename result_of<F(Args...)>::type>

  {
    using returnType = typename result_of<F(Args...)>::type;
    //创建一个packaged_task对象
    // packaged_task<returnType()> pkg(
    //     bind(forward<F>(f), forward<Args>(args)...));
    auto mytask = make_shared<packaged_task<returnType()>>(
        bind(forward<F>(f), forward<Args>(args)...));
    //得到future
    future<returnType> res = mytask->get_future();
    //把任务函数添加到队列
    m_queueMutex.lock();
    m_tasks.emplace([mytask]() mutable { (*mytask)(); });
    m_queueMutex.unlock();
    m_condition.notify_one();
    return res;
  }

编译成功了,接下来就是测试:

现在不需要阻塞主线程了,因为调用get会阻塞,一直到future对象里面有数据(任务函数执行的结果),现在异步线程池就完成了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值