序
线程池相比单纯的多线程有一些些好处:
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博客