《C++标准库》学习笔记 — STL — 并发 — 启动线程
一、高级接口 async() 和 Future
async() 提供一个接口,让一个 (caller object) 成为一个独立线程。
class future 允许你等待线程结束并获取其结果。
1、实例
考虑下面的操作:
func1() + func2()
在单线程中,整体处理时间是 func1() 所花时间加上 func2() 所花时间,再加上计算总和所花时间。
如果我们尝试在多线程中实现这样的操作,可以将总时间限制为 func1() 所花时间和 func2() 所花时间中的较大者加上计算总和的时间:
#include <iostream>
#include <random>
#include <future>
#include <thread>
#include <chrono>
using namespace std;
int doSomething(char c)
{
default_random_engine e(time(0));
uniform_int_distribution u(10, 1000);
for (int i = 0; i < 10; ++i)
{
this_thread::sleep_for(chrono::milliseconds(u(e)));
cout.put(c).flush();
}
return u(e);
}
int func1()
{
return doSomething('.');
}
int func2()
{
return doSomething('+');
}
int main()
{
future<int> result1(async(func1));
int result2 = func2();
int result = result2 + result1.get();
cout << endl << "result is " << result << endl;
}
我们首先使用 async() 尝试在后台运行 func1(),并将结果赋值给某个 std::future object。这种调用是异步的,相当于在一个独立的线程中执行该方法,因此不会阻塞主线程。基于两个原因,返回 future object 是必要的:
- 它允许我们获取在独立线程中执行方法的返回值(或异常)
- 它可以确保传递给目标函数对象会被执行。因为 async() 函数的调用不代表目标函数一定会开始执行。
我们调用 get() 函数会发生以下三件事之一:
- 如果 func1() 已在独立线程中启动且结束,我们可以立即获得其返回值
- 如果 func1() 被启动但尚未结束,会导致主线程停滞直到该函数运行结束,然后我们才能获取到运行结果
- 如果 func1() 尚未启动,会被强迫启动如同一个同步调用。
调用 async() 并不保证传入的函数一定会被启动和结束。如果有个线程处于可用状态,那么它的确会被启动,但如果不是这样(在单线程环境中,或线程资源已被占满),这一调用会被推迟至你明确说你需要其结果(调用 get() 时)或只是希望目标函数完成其任务(调用 wait())。
所以,我们可以定义让程序更快速的一般做法:修改程序使它受益于并行处理(如果平台支持),但仍能够在单线程环境中运作。为了到达这个目标,我们需要:
- #include <future>
- 传递某些可并行执行的函数,交给 std::async() 作为一个可调用对象
- 将执行结果赋值给一个 future object。
- 当需要那个被启动函数的执行结果,或确保该函数结束,就调用 future::get() 方法。
需要注意的是,上述原则只是用于不发生数据竞争的情况下。
另外,为优化程序性能,我们必须确保在最必要时才索取目标函数的执行结果。否则,如下的代码可能会产生意外的运行情况:
int result = func2()+ result1.get();
在C++中,上述代码中两个函数(func2() 和 get())的调用顺序是不确定的。因此,这样的调用可能完全无法实现异步执行的效果。
为了获得最佳的执行效果,我们应该将 async() 和调用 get() 之间的距离最大化。
2、发射策略
async() 函数提供了一个参数用于设置发射策略:
future<int> result1(async(launch