《Effective Modern C++》学习笔记 - Item 36: 如果异步性是必要的,指明std::launch::async

  • 如上节所述,使用 std::async 相比手动创建线程有若干优点。但是,调用 std::async 不保证传入的函数(或 callable object)一定是在其它线程异步运行的

  • std::async 实际是遵循 运行策略(launch policy) 执行的,标准库在 std::launch 枚举类中声明了两种策略(设待执行的函数名为 f ):

    • std::launch::async 运行策略表示 f 必须 被异步地(即在另一个线程上)执行。
    • std::launch::deferred 运行策略表示只有当 std::async 返回的 std::future调用了 get()wait() 时,f 才会被执行(这里做了一些简化,详见 Item 38)。换言之,f 的执行被延迟了。当 get()wait() 被调用时,f 会被 同步地 执行,调用者会被阻塞直到 f 完成。如果它们没被调用,则 f 永远不会被执行
  • 有趣的是,std::async 的默认运行策略并非这二者中的任何一个,而是它们取或的结果。因此,默认策略同时允许异步或同步的执行,这使得标准库中的线程管理能有更灵活的控制。
    在这里插入图片描述
    在这里插入图片描述

以上代码节选自MSVC的 <future> 文件。launch的实现方式是常用的 enum class 作为 flag,重点是要为其定义或、异或等位运算符,75行做的就是这件事。_BITMASK_OPS(x) 宏展开就是为类 x 定义多个运算符,其中例如 | 运算符的实现是取枚举的 underlying_type 并做位运算。因此可以使用 launch::async | launch::deferred = 01b | 10b = 11b

  • 由于默认策略可能采取 deferred 策略的特性,使用基于 wait_forwait_until 的循环等待 std::future 的结果时需要特别注意,例如下面的代码,可能永远卡在循环里:
using namespace std::literals; 	// for C++14 duration
								// suffixes; see Item 34

void f() 						// f sleeps for 1 second,
{ 								// then returns
	std::this_thread::sleep_for(1s);
}

auto fut = std::async(f); 		// run f asynchronously(conceptually)

while (fut.wait_for(100ms) != 		// loop until f has
	   std::future_status::ready) 	// finished running...
{ 									// which may never happen!
	...
}

如果 f 的执行被 deferred,那么 fut.wait_for() 将永远返回 std::future_status::deferred。解决的方法是在进入该循环前加一个 if 判断。由于没有直接获取 std::future 的任务是否被延迟的方法,我们仍可以使用 wait_for,将 timeout 设为 0 即可:

auto fut = std::async(f); 			// as above
if (fut.wait_for(0s) == 			// if task is
	std::future_status::deferred) 	// deferred...
{
 			// ...use wait or get on fut
	...		// to call f synchronously
} else { 	// task isn't deferred
 while (fut.wait_for(100ms) != 			// infinite loop not
		std::future_status::ready) { 	// possible (assuming
 										// f finishes)
	...		// task is neither deferred nor ready,
 			// so do concurrent work until it's ready
 }
	... 	// fut is ready
}
  • 当然,如果你确实想保证 std::async 的任务是被异步执行的,那么在构造时第一个参数传入 std::launch::async 即可:
auto fut = std::async(std::launch::async, f); 	// launch f
												// asynchronously
  • 如果这种情况是常见的,你也可以做一层简单包装作为一个工具函数:
template<typename FuncType, typename... ArgTypes>
inline auto realAsync(FuncType&& f, ArgTypes&&... args)
{
    return std::async(std::launch::async,
                      std::forward<FuncType>(f),
                      std::forward<ArgTypes>(args)...);
}

auto fut = realAsync(f);

总结

  1. std::async 的默认运行策略同时允许运行同步和异步的运行。
  2. 这种灵活性导致了访问 thread_local 变量时的不确定性(原文只是一笔带过,因此本篇中没有提及,有兴趣的读者自行查阅~),而且意味着任务可能永远不会被执行,影响了基于 timeout 的 wait 的逻辑。
  3. 如果任务的异步执行是必要的,(创建 std::async 时)指明 std::launch::async 的运行策略。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值