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

本文讨论了线程池相对于多线程的优势,如任务分配效率和资源管理,同时也指出了线程池在任务获取和提交锁上的时间损耗问题。通过实例对比,展示了线程池在异步带输入、返回值场景中的应用和资源消耗。
摘要由CSDN通过智能技术生成

 序

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

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

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

也有一些坏处:

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

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

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

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

#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;
}

输出

main spend time = 12.0011

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

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

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

#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;
}

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

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博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文锦渡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值