今天小豆君来讲讲并发编程中的futrue和promise,主要用于线程间的同步和异步编程
在介绍future和promise之前,我先讲讲在没有它们之前,我们是如何做异步编程的。
假设有这样一个场景,你需要从数据库中查询大量数据,此时想到使用线程查询,当查询完后,通知前端显示查询结果。
1 使用轮询处理异步调用
1)线程A给线程B发送一个指令,要求线程B读取数据库数据,当读完数据后,设置共享变量flag为true(初始为false)
2)线程A轮询flag,等待flag被置为true的时候,将数据展示到界面中。
异步轮询
代码:
#include <iostream> #include <string> #include <Windows.h> static bool finished = false; static std::string data = ""; void query_data() { Sleep(1000); data = "hello"; finished = true; } void async_poll() { std::thread(query_data).detach(); while (true) { if (finished) { break; } Sleep(10); } std::cout << "async_poll data: " << data << "\n"; } int main() { async_poll(); }
输出:
轮询输出结果
优点:编程简单,思路清晰
缺点:需要定义公共变量,代码难以维护;编写轮询代码,降低了系统性能。
2 使用回调函数处理异步调用
第二种处理异步的方法是,我们可以使用回调函数
代码
#include <iostream> #include <string> #include <Windows.h> typedef void (*Callback)(const std::string& str); void query_data(Callback cb) { Sleep(1000); std::string data = "hello"; cb(data); } void show_data(const std::string& data) { std::cout << "show_data data: " << data << "\n"; } void async_callback() { std::thread td = std::thread(query_data, show_data); td.join(); } int main() { async_callback(); }
输出:
回调输出
优点:不需要编写轮询语句,不需要共享变量。
缺点:编程复杂,不易维护,可读性低,当涉及到多个异步调用时需要多个回调函数递归嵌套(熟悉es5的同学会深有体会),非常容易出错,复杂度指数增长。
3 使用promise和future处理异步调用
promise和future包含在future头文件中 #include
promise 和 future共享一个称为共享状态(shared state)的动态分配内存的变量,这个变量的类型可以是已经定义好的,也可以使一个异常。线程能够在 future上被挂起,等待该共享变量变为可读状态,因此 future 可以进行线程同步。
共享变量直到被promise 设置为一个值或是一个异常后才变成可读状态。且只能被设置一次,否则会发生以下情况。
- 如果某个线程多次试图将共享状态设置为一个值或是一个异常,那么共享状态将会被设置为 std::future_error 异常;
- 如果某个线程从来没有将共享状态设置为一个值或是一个异常,那么在 promise 被销毁时,它的析构函数会将共享状态设置为 std::future_error 异常。
future调用get获取共享变量结果。其也有两种情况:
- 如果此时共享变量已经是可读状态,则立即返回结果;
- 如果此时共享变量不可读,则等待结果为可读时返回结果。
future和promise处理异步调用
代码:
#include <iostream> #include <future> #include <string> #include <Windows.h> void query_data(std::promise<std::string>& prom) { Sleep(1000); std::string data = "hello"; prom.set_value(data); } void async_future_promise() { std::promise<std::string> prom; std::thread(query_data, std::ref(prom)).detach(); std::future<std::string> result = prom.get_future(); std::cout << "async_future_promise data: " << result.get() << "\n"; } int main() { async_future_promise(); }
输出:
使用future和promise
看,我们的代码很简单,在主线程中创建prom,声明共享变量为string类型,将它作为引用参数传递给线程函数query_data,在线程中查完数据后,使用prom.set_value将共享变量设置为hello,此时共享变量变为可读状态。在主线程中使用future的get函数获取共享变量结果。
整个代码看起来清晰明了。这也是小豆君推荐大家使用promise和future的原因。
那么我们现在再进行一下延伸,如果我再想获取到一个int型的结果该怎么办(也就是说,创建的线程先返回string数据,再返回int数据)
其实很简单,你只需要给查询线程query_data定义两个参数即可,在函数内部分别设置两个值。
void query_data2(std::promise<std::string>& prom1, std::promise<std::int>& prom2) { Sleep(1000); std::string data = "hello"; prom1.set_value(data); Sleep(1000); prom2.set_value(100); }
好了,关于线程间异步处理的问题就先介绍到这里。
有兴趣的可以关注:
微信公众号:小豆君编程分享
头条号:小豆君编程分享