在C++中,使用std::async时应当注意哪些关键点?请列举并解释这些注意事项。
考察说明
考察对C++并发编程中std::async机制的理解和实际使用中的陷阱。
回答思路
- 【回答框架 1】std::async用于启动异步任务,返回std::future。其启动策略由参数决定:std::launch::async强制新线程执行,std::launch::deferred延迟到调用future的get或wait时才同步执行,默认策略是实现选择,可能是两者之一。
- 【回答框架 2】注意future对象的析构行为:若future是async启动且未调用get或wait,析构时会阻塞直到任务完成;但若使用deferred策略,析构不阻塞。此外,所有future共享状态时,最后一个析构可能阻塞。
- 【回答框架 3】若async启动的任务抛出异常,异常会存储在共享状态中,在调用get时重新抛出。如果不调用get或wait,异常可能被忽略,但析构仍会等待任务完成,异常不会传播,可能导致任务结果丢失。
- 【回答框架 4】注意避免过度使用async:频繁创建线程开销大,对于小任务可能不如直接调用;建议结合线程池或根据任务粒度决定。同时,注意死锁:如果任务依赖外部资源且获取顺序不当,可能死锁。
- 【回答框架 5】注意生命周期:确保async返回的future对象存活,否则任务可能立即执行并等待,但若在临时future上调用async,可能立即析构并阻塞,应避免。
- 【关键点 1】std::async返回std::future,默认策略是实现选择,可能不立即执行。
- 【关键点 2】对async启动的future,析构时若未调用get或wait,会阻塞等待任务完成。
- 【关键点 3】get()会阻塞并获取结果或抛出异常;wait()仅等待。
- 【关键点 4】异常通过共享状态传递,仅在get()时抛出。
- 【关键点 5】谨慎处理future的析构和生命周期,避免临时对象导致意外阻塞。
- 【易错点 1】误以为默认策略立即创建新线程,实际上可能是延迟执行。
- 【易错点 2】忽略future析构的阻塞特性,在循环或临时对象中引发性能问题。
- 【易错点 3】使用decay_copy导致对象复制,若任务需要引用,需注意生命周期。