1、背景
自己写std::async也不难,每次调任务就写一个,而且可以让系统自己判断资源是否紧张,然后它自己创建或者不创建新的线程去执行任务,但是很多次构造和析构是消耗资源的,于是想着用线程池。
2、大佬的代码
参考lzc/ThreadPool,fork大佬的代码,就一个文件ThreadPool.h,看都看不懂,更别说加东西了,不过可以拿来直接用就行了。
唯一能够改的就是把bool stop;改成std::atomic_bool stop。
3、使用
在第一次把线程池加到我工程的时候,就调试了一天的bug或者异常,原本我的工程就是一个主线程走到结束,现在我想在程序的开始用线程池去执行一些计算,然后在程序结束的时候取到结果。
这里就列举容易忽视的错误(我遇到的坑):
(1)线程池函数调用了将被析构的变量。解决办法是:需要将该变量以std::move的方式传递出来。接下来详细说明:
可以看到线程池函数调用了curZ_vec[i],如果不传出去的话,curZ_vec被析构,函数执行就会出现一些未知的效果,这里假设不用std::move去返回的话,依然会被析构,因为原始的内存是经过了一次拷贝后就析构掉了 。
(2)线程池函数调用的参数引用被其他线程修改。解决方式是:要么确保其他地方不会修改引用,要么深拷贝一份内存出来专门给线程池用。接下来详细说明:
我在一开始传参是cloudPtr,并且认为后面的函数没有修改cloudPtr,但是这个自以为的认为没有修改是很可怕的,你并不知道哪处地方一不小心把cloudPtr修改了,后面发现了一处很隐晦的修改cloudPtr的地方,为了不改动早期的代码,这里我就深拷贝一份cloudPtr内存出来专门给线程池用。
(3)传右值如false、true在程序多开(多进程)的时候异常。解决方式是:传constexpr bool变量。接下来详细说明:
我的程序自己调试的时候可能会开很多个进程,这里我传isSave是是否要打印和保存信息,结果发现多开的时候,依然会打印信息,所以改成传constexpr bool变量。
最后,因为第2条是我最后才发现的,这样就说明,可能上面提的第1、3条都不成立,但是我也不想测试了,以后写的时候注意就是了,std::move至少可以减少拷贝、提高效率。
再贴一段简易的使用例子:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <future>
#include <chrono>
#include "ThreadPool.h"
auto func1(int a)
{
std::cout << std::this_thread::get_id() << " is std::this_thread::get_id() -> func1\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
return std::move(std::make_pair(std::move(a + 1), std::move(a * 2)));
}
auto launch(ThreadPool& thdPool, int num)
{
std::cout << std::this_thread::get_id() << " is std::this_thread::get_id() -> launch\n";
int num2 = num * 2;
std::vector<int> idx(num2);
for (int i = 0; i < num2; ++i)
idx[i] = i;
std::vector<std::future<std::pair<int, int>>> future_vec(num2);
for (int i = 0; i < num2; ++i)
{
future_vec[i] = thdPool.enqueue(func1, std::ref(idx[i]));
}
//future_vec如果不用move的话编译就会报错,idx如果不用move的话运行会出错,因为不用move的话这里idx会被拷贝一份到pair,
//而原始的idx被析构,后续线程池在调用的时候,里面有参数idx[i]的原本的地址被析构了,拿到的东西未知
// 此外返回的std::pair或者std::tuple也需要用std::move传递才可以,至少可以减少拷贝、提高效率
// 这里虽然不加move运行多次结果依然是对的,原因是程序太小,idx的原始内存暂时还没被别的占有,还能够使用
// 要么这个idx就在函数外部被定义,然后传参进来
//return std::make_pair(std::move(future_vec), idx);//这样写有风险
return std::move(std::make_pair(std::move(future_vec), std::move(idx)));
}
void get(std::pair<std::vector<std::future<std::pair<int, int>>>, std::vector<int>>& future_vec)
{
for (auto& fu : future_vec.first)
{
auto rslt = fu.get();
std::cout << "rslt: " << rslt.first << ", " << rslt.second << std::endl;
}
}
void do_things()
{
for (int i = 0; i < 10; ++i)
{
std::cout << "i: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main()
{
ThreadPool thdPool(3);
{
auto future_vec = launch(thdPool, 4);
do_things();
get(future_vec);
}
return 0;
}