C++线程池简单使用,以及躺坑

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值