C++多线程编程之std::future
1. std::future
std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值。如果共享状态的标志不为 还没有被 Provider 设置,则调用 std::future::get() 会阻塞 当前的调用者,直到 Provider 设置了共享状态的值,std::future::get() 返回异步任务的值 或异常 (如果发生了异常)。一句话概括,std::future 是为了线程间传递数据使用的。
一共有三种 Provider:
std::async
std::promise 调用 get_future() 函数
std::packaged_task 调用 get_future() 函数
提示,std::future 独占资源,类似 unique_ptr,不可被拷贝。接下来看下三种 Provider 的用法。
2. std::async
先看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <thread> #include <future> #include <chrono> int main () { std::future<int > fu = std::async ([](int a, int b) { std::cout << "thread start!" << std::endl; return a + b; }, 10 , 20 ); std::this_thread::sleep_for (std::chrono::milliseconds (2000 )); int res = fu.get (); std::cout << "res=" << res << std::endl; }
编译的时候需要链接pthread库。
运行结果:
运行后立刻出现"thread start!"的字样,过了两秒出现结果。这是因为在运行 std::async 那一行的时候,直接会新建一个线程,执行函数的工作。这里使用的是 lambda 表达式,如果要传入自定义的函数需要加上 std::ref(函数名),传入函数引用(因为线程传入的是拷贝)。最后的 10 和 20 是运行所需的参数。之后主线程(主进程)睡眠 2000ms,阻塞在 fu.get() 上,直到线程运行完毕,最后打印结果。
综上所述,async 是为了把函数返回值带出来,用future来获取 的。
这里注意有一个坑,如果不用 fu 来接收 std::async 的话,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <thread> #include <future> #include <chrono> int main () { auto fu = std::async ([](int a, int b) { std::this_thread::sleep_for (std::chrono::milliseconds (2000 )); std::cout << "thread start!" << std::endl; return a + b; }, 10 , 20 ); std::cout << "main" << std::endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <thread> #include <future> #include <chrono> int main () { std::async ([](int a, int b) { std::this_thread::sleep_for (std::chrono::milliseconds (2000 )); std::cout << "thread start!" << std::endl; return a + b; }, 10 , 20 ); std::cout << "main" << std::endl; }
这两段代码运行结果有什么不同呢?
第一个用 fu 来接收了 std::future,导致这个 future 对象还没有被析构。所以是先打印 main,后面再过两秒打印出 thread start ,之后 future 对象才因为程序结束离开作用域被析构。
第二个代码因为没有接收 future 对象,导致这个右值直接析构。所以主进程等待 future 析构完成再继续往下走,所以打印是先等待两秒,之后打印 thread start,紧接着打印 main 。
3. std::promise
直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <thread> #include <future> #include <chrono> int main () { std::promise<int > pr; auto fu = pr.get_future (); std::thread t1 ([](std::promise<int >& p, int a){ std::cout << "thread start!" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(2000 )); p.set_value(a); std::cout << a << std::endl; }, std::ref(pr), 2 ) ; std::cout << "main" << std::endl; std::cout << "fu.get()=" << fu.get () << std::endl; t1.join (); }
返回结果:
1 2 3 4 main fu.get()=thread start! 2 2
首先promise返回了一个 future 对象,表示 promise 承诺要传一个数给 future。之后运行线程 t1,给他传入了 promise 和一个数。在线程中,给 promise 设置了一个值。此时外面的 future 就可以调用 get() 方法获得线程中设置的 a。至于打印为何会断开,是因为在调用 fu.get() 的时候阻塞在那里了,所以先把前半段打印出来,等待 promise 传值给 future。
综上所述,promise 是用来和 future 绑定,给他传数据 的。
3. std::packaged_task
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <thread> #include <future> #include <chrono> int main () { std::packaged_task<int (int , int ) > ta ([](int a, int b) { std::cout << "thread start!" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(2000 )); return a + b; }) ; auto fu = ta.get_future (); std::cout << "start to create thread" << std::endl; std::thread t1 (std::ref(ta), 2 , 3 ) ; std::cout << "fu.get()=" << fu.get () << std::endl; t1.join (); }
1 2 3 start to create thread fu.get()=thread start! 5
packaged_task 和一个普通的仿函数没有区别,只是返回值可以通过 future 来接收。结果同上,会在调用 fu.get() 的时候阻塞,等待线程 t1 执行完毕。
综上所述,packaged_task 是用来将一个 future 和一个函数绑定,返回函数执行结果 的。和 async 的区别是它可以延迟启动,由 thread 来调用。当然你想要使用
1 2 ta (2 , 3 );std::cout << fu.get ();
那也可以,毕竟他也是个仿函数。