C++ std::async()和std::future:异步执行函数(附带实例)
线程可使我们在同一时间运行多个函数,这将帮助我们充分利用多处理器或多核系统的硬件。然而,线程要求显式、底层的操作。相比线程的另一选择是任务,在特定线程中运行一组工作。
C++ 标准没提供完备的任务库,通过 promise-future 通道,开发者可在不同的线程中异步执行函数并传递执行结果。在本节中,我们将看到如何使用 std::async() 和 std::future 实现这一点。
在本节的示例中,我们将使用如下函数:
1) 使用 std::async() 启动新线程来执行特定函数。创建异步生产者并返回与之关联的 future。为了保证函数异步执行,将 std::launch::async 策略作为第一个参数传递给函数:
2) 继续执行当前线程:
3) 当你需要保证异步操作执行完毕时,在 std::async() 返回的 future 对象上调用 wait() 方法:
在当前线程继续执行直到线程需要来自异步函数的结果时,在工作线程上异步执行函数,可以执行以下操作:
1) 使用 std::async() 启动新线程来执行特定函数,创建异步生产者并返回与之关联的 future。为了保证函数异步执行,将 std::launch::async 策略作为第一个参数传递给函数:
2) 继续执行当前线程:
3) 当你需要异步函数执行的结果时,可在 std::async() 返回的 future 对象上调用 get() 方法:
当标志(std::launch::async|std::launch::deferred)都被指定时,任务在新线程被异步执行还是在当前线程同步执行,取决于实现。这是 std::async() 重载的行为,它不指定启动策略,这个行为是不确定的。
不要使用不确定的 std::async() 重载来异步运行任务。因此,总是使用要求启动策略的重载,并总是只使用 std::launch::async。
std::async() 的两个重载都返回一个 future 对象,future 对象指向由 std::async() 为其建立的 promise-future 通道内部创建的共享状态。当你需要异步操作的结果时,在 future 上调用 get() 方法。这将阻塞当前线程直到结果值或异常可用。如果 future 不传递任何值或你对值不感兴趣,但你想要保证异步操作在某个时刻完成,则可以使用 wait() 方法,它会阻塞当前线程直到可通过 future 获取共享状态。
future 类还有两个额外的等待方法:
这些方法可被用于创建轮询程序,为用户显示状态,如下示例所示:
C++ 标准没提供完备的任务库,通过 promise-future 通道,开发者可在不同的线程中异步执行函数并传递执行结果。在本节中,我们将看到如何使用 std::async() 和 std::future 实现这一点。
在本节的示例中,我们将使用如下函数:
void do_something() { // simulate Long running operation { using namespace std::chrono_literals; std::this_thread::sleep_for(2s); } std::lock_guard<std::mutex> lock(g_mutex); std::cout << "operation 1 done" << '\n'; } void do_something_else() { // simulate Long running operation { using namespace std::chrono_literals; std::this_thread::sleep_for(1s); } std::lock_guard<std::mutex> lock(g_mutex); std::cout << "operation 2 done" << '\n'; } int compute_something() { // simulate long running operation { using namespace std::chrono_literals; std::this_thread::sleep_for(2s); } return 42; } int compute_something_else() { // simulate Long running operation { using namespace std::chrono_literals; std::this_thread::sleep_for(1s); } return 24; }本节中,我们将使用 future,async() 和 future 都在 <future> 头文件的 std 命名空间中可用。
C++ std::async()和future的使用方式
在当前线程继续执行而不需要等待结果时,在另一线程上异步执行函数,可如下做:1) 使用 std::async() 启动新线程来执行特定函数。创建异步生产者并返回与之关联的 future。为了保证函数异步执行,将 std::launch::async 策略作为第一个参数传递给函数:
auto f = std::async(std::launch::async, do_something);
2) 继续执行当前线程:
do_something_else();
3) 当你需要保证异步操作执行完毕时,在 std::async() 返回的 future 对象上调用 wait() 方法:
f.wait();
在当前线程继续执行直到线程需要来自异步函数的结果时,在工作线程上异步执行函数,可以执行以下操作:
1) 使用 std::async() 启动新线程来执行特定函数,创建异步生产者并返回与之关联的 future。为了保证函数异步执行,将 std::launch::async 策略作为第一个参数传递给函数:
auto f = std::async(std::launch::async, compute_something);
2) 继续执行当前线程:
auto value = compute_something_else();
3) 当你需要异步函数执行的结果时,可在 std::async() 返回的 future 对象上调用 get() 方法:
value += f.get();
C++ std::async()和future的工作原理
std::async() 变长参数函数模板有两个重载:一个指定启动策略作为第一个参数,另一个则不是。std::async() 的其他参数为执行的函数和对应的参数。启动策略由有作用域的枚举 std::launch 定义,在<future>
头文件中可用:
enum class launch : /* unspecified */ { async = /* unspecified */, deferred = /* unspecified */, /* implementation-defined */ };这两个启动策略指定了以下内容:
- 使用 async,新线程被启动以异步执行任务;
- 使用 deferred,任务在调用线程第一次请求其值时执行。
当标志(std::launch::async|std::launch::deferred)都被指定时,任务在新线程被异步执行还是在当前线程同步执行,取决于实现。这是 std::async() 重载的行为,它不指定启动策略,这个行为是不确定的。
不要使用不确定的 std::async() 重载来异步运行任务。因此,总是使用要求启动策略的重载,并总是只使用 std::launch::async。
std::async() 的两个重载都返回一个 future 对象,future 对象指向由 std::async() 为其建立的 promise-future 通道内部创建的共享状态。当你需要异步操作的结果时,在 future 上调用 get() 方法。这将阻塞当前线程直到结果值或异常可用。如果 future 不传递任何值或你对值不感兴趣,但你想要保证异步操作在某个时刻完成,则可以使用 wait() 方法,它会阻塞当前线程直到可通过 future 获取共享状态。
future 类还有两个额外的等待方法:
- wait_for() 在一段时间后结束调用,即使在 future 上共享状态不可获取也会返回;
- wait_until() 在指定时间点后返回,即使共享状态不可用。
这些方法可被用于创建轮询程序,为用户显示状态,如下示例所示:
auto f = std::async(std::launch::async, do_something); while(true) { using namespace std::chrono_literals; auto status = f.wait_for(500ms); if(status == std::future_status::ready) break; std::cout << "waiting..." << '\n'; } std::cout << "done!" << '\n';此程序的运行结果如下:
waiting...
waiting...
waiting...
waiting...
operation 1 done
done!