C++用boost实现带返回值、输入参数的异步线程池

线程池相比单纯的多线程有一些些好处:

1、线程池会自动分配工作给一个空闲的工作线程来执行,比按批次执行的多线程更优;

2、线程池只需要开始创建一次,多线程多次使用时的创建、销毁会造成资源浪费;

也有一些坏处:

1、线程在任务队列中获取任务以及向任务队列中提交任务都需要抢占队列的互斥锁,会造成时间损耗,尤其在任务数多,每个任务需要的时间不是很长的情况下,抢占任务队列互斥锁的时间损耗就显得更加明显。例如,在16核机器,线程池开启14个线程,向线程池中提交2000个task(每个task耗时1ms 左右)的情况下,向线程池提交任务所需时间约20ms。因此,线程池的方式更适合每个task消耗的时间比较长,任务数不是特别多的场景。

异步带输入、返回值的多线程实现

异步带输入、返回值的多线程直接使用boost::async实现即可,通过future.get()获得,但想要构建线程池貌似不能直接用boost::async实现。

先暂且记录一下 异步带输入、返回值的多线程实现

cpp 复制代码
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>
using namespace std;

std::pair<int, double> sleep_print(std::vector<int> seconds) {
    double sum = 1;
    for (auto sec : seconds) {
        std::cout << std::this_thread::get_id() << " :start sec :" << sec << endl;

        // for (int i = 1; i < 100; i++) {
        //     sum *= i;
        // }
        sleep(sec);
    }
    return std::pair<int, double>(seconds[0], sum);
}
int main() {
    auto t0 = chrono::system_clock::now();
    int thread_num = 4;
    std::vector<std::vector<int>> thread_init_poses;
    thread_init_poses.resize(thread_num);
    //分配任务组
    for (int i = 0; i < 9; i++) {
        int i_num = i % thread_num;
        thread_init_poses[i_num].push_back(i);
    }
    // 多线程执行任务组
    std::vector<boost::unique_future<std::pair<int, double>>> futures;
    for (int i = 0; i < thread_num; i++) {
        futures.emplace_back(boost::async(boost::bind(&sleep_print, thread_init_poses[i])));
    }

    boost::wait_for_all(futures.begin(), futures.end());
    // 通过future获取结果
    for (auto& future : futures) {
        auto res = future.get();
        cout << res.first << ", " << res.second << endl;
    }

    auto t2 = chrono::system_clock::now();
    cout << "main spend time = " << double((t2 - t0).count()) / 1000 / CLOCKS_PER_SEC << endl;
    return 0;
}

输出

cpp 复制代码
main spend time = 12.0011

异步带输入、返回值的线程池实现

尝试了threadpool库,貌似不能直接获得返回值,需要通过回调函数获得,好像还不可以传到主函数,就挺麻烦的,见参考文献1

最后终于用boost::asio::io_service和boost::thread_group来实现成功了,将其封装成了一个类,但没有搞成模板(又菜又懒....)

cpp 复制代码
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>
using namespace std;
// test class
typedef boost::packaged_task<std::pair<int, double>> task_t;
typedef boost::shared_ptr<task_t> ptask_t;

std::pair<int, double> sleep_print(int seconds) {
    std::cout << std::this_thread::get_id() << " :start seconds :" << seconds << endl;
    double sum = 1;
    // for (int i = 1; i < 100; i++) {
    //     sum *= i;
    // }
    sleep(seconds);
    return std::pair<int, double>(seconds, sum);
}

class ThreadPool {
private:
    boost::thread_group threads;
    boost::asio::io_service io_service;

public:
    boost::shared_ptr<boost::asio::io_service::work> work;
    ThreadPool(int num);
    ~ThreadPool();

    void push_job(int seconds, std::vector<boost::shared_future<std::pair<int, double>>>& futures);
};

ThreadPool ::ThreadPool(int num) {
    cout << "ThreadPool ::ThreadPool" << endl;
    work.reset(new boost::asio::io_service::work(io_service));
    for (int i = 0; i < num; ++i) {
        threads.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
    }
}

ThreadPool ::~ThreadPool() {
    threads.join_all();
    cout << "ThreadPool ::~ThreadPool" << endl;
}

void ThreadPool::push_job(int seconds, std::vector<boost::shared_future<std::pair<int, double>>>& futures) {
    cout << "push_job :" << seconds << endl;
    ptask_t task = boost::make_shared<task_t>(boost::bind(&sleep_print, seconds));
    boost::shared_future<std::pair<int, double>> future(task->get_future());
    futures.push_back(future);
    // io_service.reset();
    io_service.post(boost::bind(&task_t::operator(), task));
}

int main() {
    auto t0 = chrono::system_clock::now();
    ThreadPool thread_pool(4);
    std::vector<boost::shared_future<std::pair<int, double>>> futures;

    for (int i = 0; i < 9; i++) {
        thread_pool.push_job(i, futures);
    }

    for (boost::shared_future<std::pair<int, double>> future : futures) {
        auto data = future.get();
        cout << data.first << ", " << data.second << endl;
    }

    for (int i = 0; i < 9; i++) {
        thread_pool.push_job(i, futures);
    }

    for (boost::shared_future<std::pair<int, double>> future : futures) {
        auto data = future.get();
        cout << data.first << ", " << data.second << endl;
    }

    thread_pool.work.reset();
    auto t2 = chrono::system_clock::now();
    cout << "main spend time = " << double((t2 - t0).count()) / 1000 / CLOCKS_PER_SEC << endl;
    return 0;
}

写了个简单的测试用例,最终输出为

cpp 复制代码
main spend time = 24.0013
ThreadPool ::~ThreadPool

运行时间为24s则代表我们设置的两组9个任务会序灌到4个线程执行(最久时间为(0+4+8)*2=24s)

声明一个ioService work 的原因是为了保证io service 的run方法在这个work销毁之前不会退出

看这两组程序跑相同数据处理运行时间都是12s(线程池示例跑了两遍所以是24s),咋看没什么区别,然而这个是由于任务分配机制刚好一模一样导致的,这必然会导致每个线程运行相同的sleep(second)。在真实情况下,每个任务的耗时是不可提前预知的,这样用多线程可能会出现其他个线程跑完了对应批次的任务,而全都在等待最慢那个批次任务的情况,线程池则可以让先跑完的参与到剩下的任务中。

另外,如果是多线程在每次运行函数时都要创建删除,也会浪费资源,线程池只需要在开始时进行维护。

参考文章

c++11:线程池,boost threadpool、thread_group example_c++线程池 基于boost-CSDN博客

C++ Thread Pool 使用解析 | Ce39906's Blog

boost::asio::io_service创建线程池简单实例_asio 线程池-CSDN博客

相关推荐
Heisenberg~几秒前
详解C++类与对象(三)
c++
重生之我是数学王子19 分钟前
QT 网络编程 数据库模块 TCP UDP QT5.12.3环境 C++实现
数据库·c++·qt·udp·tcp
DY009J44 分钟前
如何搭建C++环境--1.下载安装并调试Microsoft Visual Studio Previerw(Windows)
c++·microsoft·visual studio
nuo5342021 小时前
The 2024 ICPC Kunming Invitational Contest
c语言·数据结构·c++·算法
小王同学的C++1 小时前
移动语义和拷贝语义有什么区别?
c++
霁月风1 小时前
设计模式——装饰器模式
c++·设计模式·装饰器模式
叫我龙翔1 小时前
【项目日记】仿mudou的高并发服务器 --- 实现缓冲区模块,通用类型Any模块,套接字模块
linux·运维·服务器·网络·c++
誓约酱2 小时前
(动画)Qt控件 QLCDNumer
开发语言·c++·git·qt·编辑器
@小博的博客2 小时前
C++初阶学习第十三弹——容器适配器和优先级队列的概念
开发语言·数据结构·c++·学习
离歌漠2 小时前
C#调用C++ DLL方法之P/Invoke
c++·c#·p/invoke