为何《C++并发编程实战》中代码2的std::experimental::future实现更优?
代码1与代码2的异步实现差异解析
两段代码均出自《C++并发编程实战第2版》,用于模拟用户登录的三步流程:身份验证→请求账户信息→更新显示。二者的核心差异在于是否实现了真正的异步IO,以下是具体拆解:
代码1的执行逻辑(伪异步)
先看spawn_async模板函数:它的本质是创建新线程同步执行传入的函数,再用promise/future包装执行结果。具体到process_login流程:
- 调用
spawn_async时,会启动一个新线程,在该线程中同步阻塞执行backend.authenticate_user——这个线程会一直等待后端返回验证结果,期间完全闲置,直到IO完成才通过promise传递结果。 - 第一个
then回调被触发后,id.get()会阻塞当前执行回调的线程,同步调用backend.request_current_info,同样要等待IO完成才能进入下一个回调。 - 整个流程的每一步IO操作,都对应一个被阻塞的线程,线程资源在IO等待期间被浪费,属于用多线程模拟异步的伪异步实现。
template<typename Func> std::experimental::future<decltype(std::declval<Func>()())> spawn_async(Func&& func){ std::experimental::promise< decltype(std::declval<Func>()())> p; auto res=p.get_future(); std::thread t( [p=std::move(p),f=std::decay_t<Func>(func)]() mutable{ try{ p.set_value_at_thread_exit(f()); } catch(...){ p.set_exception_at_thread_exit(std::current_exception()); } }); t.detach(); return res; } std::experimental::future<void> process_login( std::string const& username, std::string const& password) { return spawn_async([=](){ return backend.authenticate_user(username, password); }).then([](std::experimental::future<user_id> id){ return backend.request_current_info(id.get()); }).then([](std::experimental::future<user_data> info_to_display){ try{ update_display(info_to_display.get()); } catch(std::exception& e){ display_error(e); } }); }
代码2的执行逻辑(真异步)
代码2依赖backend提供的async_*方法,这些方法是基于操作系统异步IO机制实现的真正异步操作:
backend.async_authenticate_user发起身份验证请求后,会立即返回future,不会阻塞任何线程——底层会利用IO多路复用(如epoll、kqueue)或异步IO接口(如IOCP)等待后端响应,等待期间线程可以被调度去处理其他任务。- 当验证完成、
future就绪时,第一个then回调触发,调用backend.async_request_current_info同样是发起异步请求,立即返回future,无线程阻塞。 - 整个流程没有线程在IO等待期间闲置,仅在回调需要执行计算逻辑时占用线程,线程利用率极高,是真正的异步编程模式。
std::experimental::future<void> process_login( std::string const& username, std::string const& password) { return backend.async_authenticate_user(username, password).then( [](std::experimental::future<user_id> id){ return backend.async_request_current_info(id.get()); }).then([](std::experimental::future<user_data> info_to_display){ try{ update_display(info_to_display.get()); } catch(std::exception& e){ display_error(e); } }); }
核心差异总结
- 线程资源利用:
- 代码1:每一步IO操作都占用一个阻塞线程,高并发场景下会创建大量线程,导致上下文切换开销剧增,系统资源耗尽。
- 代码2:仅在回调执行时占用线程,IO等待无阻塞,少量线程即可支撑大量并发请求,扩展性极强。
- 异步本质:
- 代码1是"线程化的同步":用多线程让主线程不阻塞,但工作线程仍在同步等待IO,没有摆脱阻塞模型。
- 代码2是"非阻塞异步IO":依赖操作系统底层机制实现无等待的IO请求,是异步编程的核心实现方式。
- 性能表现:
- 代码1在低并发场景下看似正常,但高并发时性能急剧下降。
- 代码2的性能不会随并发量线性恶化,能高效处理大规模IO密集型任务。
内容的提问来源于stack exchange,提问作者牛牪犇
相关产品推荐
相关产品推荐

