C++ Future and Promise

本文详细介绍了C++的future、promise和async()的使用,包括如何解决线程间的数据同步和通信问题,以及async()的线程创建、参数传递和多线程应用。示例代码展示了如何避免data race和同步线程间的操作。
摘要由CSDN通过智能技术生成

C++ future and promise and async():

本文详细介绍C++的future, promise以及async()功能。
使用这几个功能需要在source code里面加上#include<future>



一个简单的例子:

新建一个线程,计算N的阶乘,并存储到x中。一个简单的实现是这个样子的:

void factorial(int N, int& x){
	int res = 1;
	for(int i = N; i > 1; i--){
		res *= i;
	}
	x = res;
}

int main(){
	int x;
	std::thread t(factorial, 4, std::ref(x));
	t1.join();
	std::cout << "factorial of " << N << " is " << x << std::endl;
	return 0;
}

但是这个一段code有两个问题:

  1. x被主线程和子线程共享,假如主线程也想要修改x的话,可能会有data race
  2. 主线程并不知道应该什么时候获取x,有可能在主线程获取x的时候,子线程并没有完成阶乘计算

第一个问题可以使用std::mutex解决:开始计算前,子线程先获取一个mutex,防止别的线程修改x。
第二个问题需要使用std::condition_variable:当子线程完成计算,通过设置condition variable来通知主线程,阶乘计算已经完成。主线程得到消息之后才去获取x。

修改过后的代码应该是这样的:

using namespace std;
bool completed = false;
std::mutex mtx;
std::condition_variable cond;

void factorial(int N, int& x){
	int res = 1;
	for(int i = N; i > 1; i--){
		res *= i;
	}
	mtx.lock();
	x = res;
	completed = true;
	mtx.unlock();
	cond.notify_all();
}

int main(){
	int x;
	std::thread t(factorial, 4, std::ref(x));
	t1.join();
	std::unique_lock<std::mutex> lock(mtx);
	while(!completed){
		cond.wait(lock);
	}
	std::cout << "factorial of " << N << " is " << x << std::endl;
	return 0;
}

可以看出,为了用线程实现一个简单的阶乘计算,我们引入了三个全局变量:一个bool变量用于指示计算是否完成,一个互斥锁用于防止两个线程同时修改同一个变量,一个condition variable用于线程之间进行synchronization通信。

所以C++引入了一个概念叫做async(),来解决这个问题:



async()的基本使用:

async()的语法如下所示:

std::future<T> async(function&& f, Args&& … args);

async()会异步运行传入的function,这个function可能是由当前线程完成的,也有可能是一个新建的线程完成的。
async()会返回一个std::future类型的值(function本身返回类型并不是std::future,function返回的值被存储在std::future中,然后async返回这个std::future类型)。
如果使用async()来改写上面的例子,那么得到的code应该是这样的:

#include<future>

int factorial(int N){
	int res = 1;
	for(int i = N; i > 1; i--){
		res *= i;
	}
	return res;
}

int main(){
	int x;
	std::future<int> f = std::async(factorial, 4);
	// f.get()会一直等到子线程or当前线程完成计算之后才返回结果
	x = f.get();
	// f.get() /* 再次调用f.get()程序会crash */
	std::cout << "factorial of " << N << " is " << x << std::endl;
	return 0;
}

通过使用async,我们消除了三个global variable,并且不必显式声明线程。
注意:每个future只能使用一次future.get(),如果在第一次使用get()之后再次调用,程序会出错



进阶1:线程的创建

前面提到,async()可能会创建新的线程来执行传入的function,也可能用当前线程来执行传入的function。实际上,我们可以使用参数来控制是否要新建function,语法如下:

std::async(std::launch::deferred, function&& f, args&& …args);

std::launch::deferred表示不需要新建线程,使用当前线程来完成function。并且,该线程会一直等到std::future.get()被调用的时候,才开始执行传入的function。

std::async(std::launch::async, function&& f, args&& …args);

std::launch::async表示创建一个新的线程来执行当前传入的function

进阶2:参数的传递

在前面的例子中,都是子线程向主线程传递参数,主线程通过std::future.get()来获取传回的参数。
其实我们也可以让主线程给子线程传递参数。为此我们需要使用前面没有用到的std::promise

#include<future>

int factorial(std::future<int>& f){
	int res = 1;
	// 子线程一直在这里等待,直到主线程传给它这个参数
	int N = f.get();
	for(int i = N; i > 1; i--){
		res *= i;
	}
	return res;
}

int main(){
	int x;
	
	// 这里,主线程向子线程保证,我会传给你一个参数,但是我现在没有这个参数,未来我会传给你。
	std::promise<int> p;
	std::future<int> f_to = p.get_future();
	
	std::future<int> f_from = std::async(factorial, std::ref(f_to));
	
	// 在不远的未来,主线程履行承诺,传给子线程一个参数:
	p.set_value(4);
	
	x = f_from.get();
	std::cout << "factorial of " << N << " is " << x << std::endl;
	return 0;
}

在上面的code中,主线程向子线程承诺传递一个参数(std::promise<int> p),子线程先接受这个承诺(std::future f = p.get_future()),然后一直等待(f.get()),直到主线程履行承诺(p.set_value()),子线程才继续向下执行。


但是承诺就是承诺,主线程完全可以不履行这个承诺。如果这种情况发生,那么子线程会出std::future_erro::broken_promise的错误。

如果主线程知道自己无法履行承诺,那么应该这样做:

#include<future>

int factorial(std::future<int>& f){
	int res = 1;
	// 子线程一直在这里等待,但是这次它发现主线程并没有履约,而是传来一个消息,于是子线程得到一个run time error消息并退出。
	int N = f.get();
	for(int i = N; i > 1; i--){
		res *= i;
	}
	return res;
}

int main(){
	int x;
	
	// 这里,主线程向子线程保证,我会传给你一个参数,但是我现在没有这个参数,未来我会传给你。
	std::promise<int> p;
	std::future<int> f_to = p.get_future();
	
	std::future<int> f_from = std::async(factorial, std::ref(f_to));
	
	// 后来主线程发现自己没有办法履行承诺,就传给子线程一个消息:
	p.set_exception(std::make_exception_ptr(std::runtime_errpr("Sorry I cannot keep my promise")));	
	
	x = f_from.get();
	std::cout << "factorial of " << N << " is " << x << std::endl;
	return 0;
}


进阶3:多线程

前面提到,一个future只能使用一次get()来读取参数,那么如果我们有N个线程想要使用get()来获取参数怎么办?
一个方法是主线程创建N个promise和future,当然这是很不可取的,C++提供了另一个方法:

std::shared_future<T>

#include<future>

int factorial(std::shared_future<int> f){
	int res = 1;
	// 现在传入给function的是一个sharable的参数f,线程可以随意使用get()而不必担心出错
	int N = f.get();
	for(int i = N; i > 1; i--){
		res *= i;
	}
	return res;
}

int main(){
	int x;
	
	std::promise<int> p;
	std::future<int> f = p.get_future();
	// 多个线程可以使用sf来调用sf.get()获取想要的参数
	std::shared_future<int> sf = f.share();
	
	std::future<int> f1 = std::async(factorial, sf);
	std::future<int> f2 = std::async(factorial, sf);
	std::future<int> f3 = std::async(factorial, sf);
	//...
	
	return 0;
}

本文译自YT视频:
https://www.youtube.com/watch?v=SZQ6-pf-5Us&ab_channel=BoQian

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值